Skip to content

docs(start-sdk): document retiring a task's replay key - #3626

Open
MattDHill wants to merge 1 commit into
masterfrom
docs/task-replayid-orphans
Open

docs(start-sdk): document retiring a task's replay key#3626
MattDHill wants to merge 1 commit into
masterfrom
docs/task-replayid-orphans

Conversation

@MattDHill

Copy link
Copy Markdown
Member

Why

A user updated StartOS, Knots, and DATUM Gateway and landed in an unrecoverable loop: two critical tasks on bitcoind's blocknotify, each demanding a different URL, each re-arming the moment the other was satisfied. DATUM was stopped the whole time, and no sequence of UI actions could settle it.

The cause is a documented-but-incomplete contract. createTask derives its replayId from [package-id]:[action-id], so when Knots renamed its config action other-configautoconfig, DATUM's next release minted a new key rather than updating the old one. Nothing rewrites or reaps the abandoned key — it sat in the database still enforcing http://datum.startos:7152/NOTIFY from a pre-2.0 release, while the live key demanded the bridge address. The OS honors the legacy task shape (db/model/package.rs), so the orphan stayed fully functional.

The guide covered replayId idempotency but never the flip side, and tasks.md actively asserted the opposite:

You can safely re-run your init function on every container rebuild without accumulating stale tasks.

True only while the key itself is stable. That sentence is a fair part of why this went unnoticed.

An audit of the fleet found the same orphan in six other bitcoind dependents (lnd, fulcrum, mempool, public-pool, electrs, umbrel-bitcoin-ui). Those are currently harmless only because their demanded values happen to be unchanged — the next edit to any of those accept/set blocks reproduces this bug. A second class exists where the retired action was deleted rather than renamed; those are worse, because recheck_tasks can no longer resolve the action's input, the task's active flag freezes, and a frozen active: true + critical stops the package with no in-product remedy.

What changed

tasks.md

  • Qualified the idempotency claim and linked it to the new section.
  • Added Retiring a replay key: what changes the key, that clearing it belongs to the package that created the task, the migration pattern, and why neither StartOS nor the SDK can do it automatically — the OS can't distinguish a retired key from one not written this run, and the SDK can't reap "keys I didn't create this run" because tasks are legitimately raised from init, from actions, and by other packages.
  • Documented both failure modes, the conditional-task case (citing fulcrum-bch-startos's NODE_TASK_KEYS + clearTask, which already solves this), and the start-cli package action clear-task … --force recovery path for boxes already stuck.

dependencies.md

  • Expanded the one-line replayId note and cross-linked the new section, since that's where someone wiring up a cross-service task is actually reading.
  • Fixed the severity union in the API signature: it read 'critical' | 'high' | 'medium' | 'low', but TaskSeverity is 'optional' | 'important' | 'critical'. Unrelated to the incident, corrected in passing.

Docs-only; no SDK surface change, so no version bump or changelog entry.

🤖 Generated with Claude Code

`createTask` derives its `replayId` from `[package-id]:[action-id]`, so
renaming an action, retargeting a task, or pointing it at a different
package writes a new key and abandons the old one. Nothing rewrites or
reaps the abandoned key, and the guide never said so — `tasks.md` in fact
claimed the opposite, that init could be re-run "without accumulating
stale tasks".

It bites in two ways. If the retired action still exists, both keys stay
live and re-arm each other; where the demanded values diverged, the user
ping-pongs between them with no way to settle. If the retired action is
gone, `recheck_tasks` can no longer resolve its input, the task's `active`
flag freezes, and a frozen active + critical task stops the package with
no in-product remedy.

Document the rule — clearing the retired key is the job of the package
that created the task, in the migration for the version that changes it —
along with why neither StartOS nor the SDK can do it automatically, the
conditional-task case, and the CLI recovery path for boxes already stuck.

Also correct the `severity` union in `dependencies.md`, which read
'critical' | 'high' | 'medium' | 'low' rather than the actual
TaskSeverity of 'optional' | 'important' | 'critical'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant