Fix migration concurrency and recovery - #120
Conversation
| proposed = map[bool]string{true: seed, false: renderTitle(status, subject, action)}[status == ""] | ||
| record.Pending = &pendingProposal{ToolUseID: toolUseID, BaseSubject: subject, Prior: task.Title, Proposed: proposed, Status: status, Action: action} | ||
| if status == "" && seed == homeTitle && attempt == "" { | ||
| record.Original, record.Last = cmp.Or(record.Original, stripStatusIcons(current)), homeTitle |
There was a problem hiding this comment.
🟡 Fresh uninstall cannot restore home title
Original is captured only when this Pre hook handles the install-time home sentinel. The installed skill explicitly supports a fresh install where Codex has not reloaded the newly written hook; in that supported path the native home-title call runs with no hook, so no task record or Original value is stored. Later uninstall cleanup sees the live title equal to homeTitle, but the fallback is stripStatusIcons(homeTitle), which is still 🧵🐻 ThreadBear 🐻🧵; uninstall can therefore complete while leaving the former home permanently branded instead of restoring its pre-install title. The original title needs to be captured outside the newly installed hook (or hook participation must be required) before the sentinel mutation.
(Refers to line 174)
Your feedback helps Open SWE learn. React with 👍 or 👎 to tell us if this review comment was useful.
There was a problem hiding this comment.
Fixed in 44d6b87. Confirmed install now reads the authoritative persisted home task before binary replacement and records its title on first claim. Follow-up review also exposed decorated-sentinel and legacy-v2.2.0 cases: cleanup now restores bare or decorated sentinels, real format-3 state gets an upgrade-safe subject fallback, and a tagged-v2.2.0 live canary exercised the exact Pre/Post upgrade-through-uninstall path. Focused regressions passed 100x; full test/race/vet and the 2,000-line gate pass.
What changed
Prior, exactProposed, or disappeared targets can reconcile. Unknown results and drift remain fail-closed.Root cause
Codex Desktop can expose two IDs for one delegated controller: a creation/supervision handle and the native runtime task ID used by hooks and title calls. ThreadBear previously required them to match exactly, so a healthy controller could fail registration.
Recovery had several independent gaps: partial pre-controller installs could not enter uninstall, first-install tasks might not reload the new hook in time to capture their original title, decorated home sentinels were not recognized during cleanup, terminal failed waves had no reachable settlement path from the persistent home, and legacy v2.2.0 state had no
Originalfield. The final correction handles each at the narrowest authoritative boundary without adding retries or trusting guessed identities.User impact
ThreadBear accepts the controller identity Codex itself supplies from the marked controller task while preserving the meaningful safety boundaries: exact home delegation, one controller, exact native targets, pending-operation reconciliation, durable uninstall ownership, archive restoration, and binary-last teardown. Classifier JSON exists only in temporary installer tasks and is not present in ordinary ongoing sessions.
Validation
44d6b873b87d7fa5857c2e914759a437344d0e881fa322fbf6dc7dec215cf941fb96dcego test ./... -count=1go test -race ./... -count=1go vet ./...git diff --checkTwo full live install/uninstall cycles against candidate
09fc5241…(the normal migration write path is unchanged in the final candidate):The accepted ceiling is full-list migration under six minutes; both cycles pass. Cycle 1's broader setup-to-active-automation time exceeded six minutes and is reported separately.
The later recovery-only production deltas were tested separately rather than misattributed to those timed runs:
15027fdc…: fresh same-turn install captured the exact home title before any newly installed hook could run, then failed-state uninstall restored it and removed every artifact.3b87d7fa…: a tagged v2.2.0 binary created genuine format-3 state; its real installed Pre/Post hook persisted the sentinel itself as the home subject; the final candidate upgraded that state to format 4 with the safe clean fallback, prepared uninstall before controller registration, restored the decorated home through a real native Pre/Post cleanup transaction, removed every artifact, and left a real unowned✅-prefixed canary title byte-exact.Four terminal-state screenshots from the two full cycles are retained in
/Users/ericlitman/.codex/visualizations/2026/08/04/019fcca7-7093-70f3-a18d-260951571103/.Reviews and upstream boundary