feat(bus): B-0444 — add worktree field to claim envelope#3043
Conversation
Implements Otto-Desktop's identified follow-up gap (B-0444 row filed in PR #3038): the claim envelope schema captured sender ID, item, and branch but not the worktree path. With multi-foreground-surface agents (Otto-CLI + Otto-Desktop on the same machine), the worktree is the per-process operational coordinate — useful as an observability signal even when sender-ID surface tags (PR #3037) already prevent split-brain across surfaces. Changes: - `ClaimPayload.worktree?: string` added to the protocol schema in tools/bus/types.ts (back-compat: optional; envelopes published before this change continue to work) - `ClaimRecord.worktree?: string` added so consumers of activeClaims / allActiveClaims see the field - `tools/bus/claim.ts acquire` accepts `--worktree <path>`; defaults to `process.cwd()` so every new claim publishes a useful worktree value with no caller change required - `check` text output appends `[worktree: <path>]` after the branch info - `check --json` and `acquire --json` include the field on the record / response Design decision recorded substrate-honestly in the B-0444 row: cross-worktree same-sender is treated as idempotent re-acquire (existing behavior preserved). Surface-tagged sender IDs (PR #3037) handle split-brain at the coordination layer; worktree is observability metadata, not a coordination key. A test pins this behavior: "same sender re-acquiring from a different worktree is idempotent (existing behavior preserved)". Tests: 14 new tests in `tools/bus/claim.test.ts` (default cwd, explicit worktree surfaces in check + --json output, combined with --branch, claim-record shape, surface IDs still arbitrate across worktrees, same-sender different-worktree idempotency). Full suite: 52/52 pass. Composes with: - B-0400 (bus protocol root) — extends the existing schema - B-0400 slice 3 / PR #2939 (claim-coordinator implementation) - PR #3037 (SENDER_IDS schema extension — sibling surface-level fix) - `.claude/rules/claim-acquire-before-worktree-work.md` — the rule this row's gap was identified against - PR #3038 (the docs(backlog) commit that filed this row) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an optional worktree field to bus claim envelopes (B-0444) so claims carry the per-process operational coordinate (which checkout produced them) for observability. Coordination/arbitration remains driven by surface-tagged sender IDs (PR #3037); worktree is metadata only, optional for back-compat.
Changes:
- Schema:
ClaimPayload.worktree?andClaimRecord.worktree?added as optional fields. - CLI:
acquire --worktree <path>flag (defaults toprocess.cwd()); surfaced in text and JSON output ofcheck/acquire. - Tests: 7 new tests covering default cwd, explicit path, JSON shape, branch+worktree combo, surface-ID arbitration still wins, same-sender different-worktree idempotency.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tools/bus/types.ts | Adds optional worktree?: string to ClaimPayload with doc comment. |
| tools/bus/claim.ts | Adds worktree to ClaimRecord, propagates through activeClaims/allActiveClaims, adds --worktree CLI flag with process.cwd() default, surfaces in text/JSON outputs. |
| tools/bus/claim.test.ts | Adds a 7-test describe block covering the new field, including back-compat (same-sender idempotency) and surface-ID arbitration. |
…ee gambit Per autonomous-loop discipline: substrate-or-it-didn't-happen. Tick shard records the three multi-Otto coordination events observed during this tick (parallel commits, branch-switches under active editing, stash-don't- discard from sibling Otto), the recovery via `git worktree add /tmp/zeta- b0444`, and the substrate-honest evidence that B-0444 itself addresses exactly the class of split-brain this tick experienced. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5f3e0473f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`parseArgs` encodes a bare `--worktree` (flag with no value following) as the literal string `"true"`. Without an explicit check, that string would flow through `flags.worktree ?? process.cwd()` and be recorded as the worktree path, making claim provenance misleading. Reject fast with a clear error so the user notices the mistyped invocation. New test pins the behavior: `bare --worktree (no value) is rejected with a clear error`. Suite: 53/53 pass. Co-Authored-By: Claude <noreply@anthropic.com>
Tick records: 6-file recovery from c0dcb26 (B-0257..B-0261 + B-0289) clearing 9 → 3 dangling-dep refs, Codex P2 bare-flag rejection landed on PR #3043 (`7740700`), thread `PRRT_kwDOSF9kNM6B6CLj` resolved. Both PRs auto-merge armed; real-dependency-wait on required CI. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 774070094b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
tools/bus/claim.ts:277
- The bare-
--worktreerejection runs before the--from/--itempresence check, so a user typo likeacquire --worktree(omitting required flags entirely) reports "--worktree requires a path argument" instead of the more informative "--from and --item are required". Consider moving the worktree validation after the required-flag checks for a more useful error ordering.
if (flags.worktree === "true") {
console.error("Error: --worktree requires a path argument");
process.exit(1);
}
const worktree = flags.worktree ?? process.cwd();
if (!from || !itemId) {
console.error("Error: --from and --item are required");
process.exit(1);
}
tools/bus/claim.ts:324
- Since
worktreeis unconditionally assigned toflags.worktree ?? process.cwd()on line 273, it is always a non-empty string. The conditional spread...(worktree ? { worktree } : {})on line 300 and the ternaryworktree ? ... : ""on line 323 can never take their false branch. This is confusing — either the default should be removed (makingworktreetruly optional in the payload) or the truthiness guards should be dropped. As written, the type signature suggests optionality the runtime never actually exercises.
payload: {
action: "claim",
itemId,
...(branch ? { branch } : {}),
...(worktree ? { worktree } : {}),
},
});
return { acquired: true, claimedByOthers: [] as string[], messageId: env.id };
}));
} catch (err) {
console.error(`Error: ${(err as Error).message}`);
process.exit(1);
}
if (!acquired) {
if (asJson) {
console.log(JSON.stringify({ itemId, acquired: false, claimedBy: claimedByOthers }));
} else {
console.error(`${itemId}: already claimed by ${claimedByOthers.join(", ")} — not acquiring`);
}
process.exit(1);
}
if (asJson) {
console.log(JSON.stringify({ itemId, acquired: true, messageId, worktree }));
} else {
const branchStr = branch ? ` (${branch})` : "";
const worktreeStr = worktree ? ` [worktree: ${worktree}]` : "";
console.log(`${itemId}: claimed by ${sender}${branchStr}${worktreeStr} — ${messageId}`);
Two round-2 threads on PR #3043 reshape the --worktree handling: 1. Codex P2 — "Accept explicit `--worktree true` values" Round-1 fix rejected `flags.worktree === "true"` unconditionally, which also rejected a legitimate `acquire ... --worktree true` call where `"true"` is the intended literal path. parseArgs is refactored to distinguish a bare flag (boolean sentinel `true`) from an explicit string value, so the bare-flag rejection now triggers on the boolean while explicit `--worktree true` flows through as a valid string path. 2. Copilot — "Defaulting worktree to process.cwd() can be misleading" Auto-defaulting to cwd records a plausible-looking coordinate when the caller is `cd`'d to an unrelated directory (CI step, wrapper script, cron context). Drop the cwd default: `--worktree <path>` to set, omit to leave the field absent. The presence of the field now reliably indicates the caller opted in. Mechanical changes: - parseArgs: return `Record<string, string | true>` instead of `Record<string, string>`; bare flags get boolean `true`. - New helpers `asString(v)` and `isSet(v)` to read flags cleanly. - Bare-flag check uses `flags.worktree === true` (the boolean). - `worktree` is `undefined` when --worktree omitted; published payload + JSON output omit the field rather than defaulting to cwd. - `asJson` switches from `=== "true"` to `isSet(flags.json)` so bare `--json` continues to work as before. Tests (54/54 pass): - Updated: "acquire omits worktree from payload + JSON when --worktree not specified (Copilot review)" — pinned absent-not-cwd behavior. - New: "explicit --worktree true (literal string) is accepted as a valid path" — pinned that round-1 false-rejection is fixed. - Existing 51 tests unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
…c0dcb26 (#3044) * recover(backlog): restore 6 lost B-0066/B-0246 decomposition rows from c0dcb26 Six P1 backlog rows were drafted in commit c0dcb26 (2026-05-08, "fix(b0066): initial marker vs index audit") on an unmerged branch and never reached main. As a result, B-0066 and B-0290 carried dangling depends_on / children references for 5 days, the backlog-ready-notifier surfaced 9 dangling-dep warnings, and the prior decomposition work was silently lost from the substrate. Recovers per `.claude/rules/honor-those-that-came-before.md` and the canonical lost-files protocol in `tools/hygiene/LOST-FILES-LOCATIONS.md`: files restored byte-for-byte from `c0dcb26`'s tree (via `git show c0dcb26:<path>`), no editorial drift. Files restored under docs/backlog/P1/: - B-0257 — MEMORY.md harness contract verification and evidence - B-0258 — MEMORY.md index generator implementation - B-0259 — MEMORY.md hook + CI drift enforcement - B-0260 — MEMORY.md cutover + parity validation - B-0261 — MEMORY.md Q1 AutoDream/AutoMemory compatibility validation - B-0289 — Green Lantern ring hardware spec + local inference requirements B-0257..B-0261 are the atomic-decomposition children of B-0066 (MEMORY.md marker-vs-index harness verify, Aaron 2026-04-28). B-0289 is a child of B-0246 (Green Lantern ring IoT device genesis seed) and is the upstream spec that B-0290 (consent gate firmware) was blocked-on. Backlog notifier result: - dangling dep refs: 9 → 3 (B-0257..B-0261, B-0289 cleared; the remaining B-0055.1, B-0054.1, B-0395 are unrelated and out of scope for this recovery) - ready-to-grind candidates: 211 → 213 (B-0289 newly buildable-now; B-0066 + B-0290 still parented so not directly ready, but children now linkable) - totalOpenRows: 376 → 382 (6 newly-visible rows) BACKLOG.md regenerated via `BACKLOG_WRITE_FORCE=1 bun tools/backlog/ generate-index.ts`; the 6 new entries appear in the P1 in-flight section under "Open work" between B-0256 and B-0262, and between B-0288 and B-0290. Worktree-isolated build (`/tmp/zeta-recover-lost`) to dodge the multi-Otto split-brain pattern documented in PR #3043's tick shard. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2228Z — lost-file recovery + Codex P2 fix on #3043 Tick records: 6-file recovery from c0dcb26 (B-0257..B-0261 + B-0289) clearing 9 → 3 dangling-dep refs, Codex P2 bare-flag rejection landed on PR #3043 (`7740700`), thread `PRRT_kwDOSF9kNM6B6CLj` resolved. Both PRs auto-merge armed; real-dependency-wait on required CI. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD032 blanks-around-lists in 2228Z tick shard markdownlint flagged the Visibility-signal list as missing a blank line between the introductory sentence and the first list item. One-line fix. Co-Authored-By: Claude <noreply@anthropic.com> * fix(backlog): add missing last_updated field to recovered B-0289 (Copilot P1) Copilot P1 on PR #3044: `tools/backlog/README.md` requires every per-row file to carry `last_updated`. The byte-for-byte recovery from c0dcb26 preserved the original frontmatter which (in B-0289 alone of the six) omitted the field; the other five carried it. Set to today's date (2026-05-13) so the field reflects when the row re-entered active substrate, not the original draft date. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Records: PRs #3043, #3044, #3045 all merged via auto-merge since previous tick (B-0444 worktree field; lost-row recovery; notifier YAML-comment fix). PR #3047 opened this tick to close the final 2 dangling-dep refs (B-0054.1 + B-0055.1 slice rows). Session-arc substrate-hygiene sweep: 9 → 2 dangling refs on main now, → 0 once #3047 merges. Co-Authored-By: Claude <noreply@anthropic.com>
* fix(backlog): restore missing slice rows B-0054.1 + B-0055.1 Two atomic decomposition slices (B-0054.1 — media-catalog schema foundation; B-0055.1 — edge-claims catalog) landed earlier as code without corresponding row files. Nine sibling rows reference these slice IDs via `depends_on`, and the backlog-ready-notifier surfaced them as dangling-dep warnings. Both rows added as `status: closed` (work landed) with bodies documenting: - the implementation file path (the canonical artifact) - retroactive acceptance criteria (all satisfied) - substrate-honest note that future slices should land row-first Backlog-notifier effect (against origin/main with PR #3045 merged): dangling dep refs: 8 → 6 (the remaining 6 clear once PR #3044 merges) ready-to-grind candidates: 211 → 217 (sibling rows unblocked) Completes the substrate-hygiene sweep: - PR #3044 recovers B-0257..B-0261 + B-0289 (file-restored from unmerged commit) - PR #3045 fixes notifier YAML inline-comment parsing - This PR formalizes the two slice IDs that never had row files Once all three land, dangling-dep count goes 9 → 0. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2241Z — slice-row restoration + 3 PRs landed on main Records: PRs #3043, #3044, #3045 all merged via auto-merge since previous tick (B-0444 worktree field; lost-row recovery; notifier YAML-comment fix). PR #3047 opened this tick to close the final 2 dangling-dep refs (B-0054.1 + B-0055.1 slice rows). Session-arc substrate-hygiene sweep: 9 → 2 dangling refs on main now, → 0 once #3047 merges. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ness cleared Records: PR #3047 merged. Verified dangling-dep refs on main went 9 → 0 (the notifier output's `(warning: N dangling dep ref(s))` suffix is now gone entirely). Speculative pickup audited `.claude/rules/` for adjacent stale callouts; found `otto-channels-reference-card.md:82` listing B-0444 as a "follow-up gap" when PR #3043 shipped it. PR #3050 replaces the framing with the merged-PR reference. Co-Authored-By: Claude <noreply@anthropic.com>
…ap" framing (#3050) * docs(rules): otto-channels card — B-0444 shipped, retire "follow-up gap" framing The Otto inter-surface communication channels reference card listed B-0444 (bus claim envelope worktree field) as a "follow-up gap" in its Composes-with section. PR #3043 shipped that feature 2026-05-13 (merged as 5db892d). Updated the entry to reference the merged PR rather than the row's open-state framing. Sibling staleness cleanup to PR #3048 (Rule 0 legacy-violations update); together they close the rule-callout staleness surfaced by this session's substrate-hygiene sweep. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2257Z — dangling refs 9 → 0 on main; otto-channels staleness cleared Records: PR #3047 merged. Verified dangling-dep refs on main went 9 → 0 (the notifier output's `(warning: N dangling dep ref(s))` suffix is now gone entirely). Speculative pickup audited `.claude/rules/` for adjacent stale callouts; found `otto-channels-reference-card.md:82` listing B-0444 as a "follow-up gap" when PR #3043 shipped it. PR #3050 replaces the framing with the merged-PR reference. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ope row closed Records: PR #3050 merged; audit of in-flight rows surfaced TWO rows both with `id: B-0444` (one shipped via PR #3043 still open; one duplicate from PM-2 gap-prediction). PR #3053 keeps B-0444 on the shipped+referenced row, renumbers the duplicate to B-0450, closes the shipped row. Co-Authored-By: Claude <noreply@anthropic.com>
…→ B-0450 (#3053) * fix(backlog): resolve B-0444 ID collision — renumber getting-started → B-0450; close shipped bus-envelope row Two rows on main both claimed `id: B-0444`: 1. `docs/backlog/P1/B-0444-getting-started-guide-...md` — filed first (commit b6e419d 17:23 via PR #3033, B-0271 PM-2 gap-prediction pass) 2. `docs/backlog/P2/B-0444-bus-claim-envelope-...md` — filed 25 min later (commit bbb984d 17:48 via PR #3038, Otto-Desktop's identified follow-up gap) AND already shipped via PR #3043 Per "first-merged-wins" the getting-started row had the ID first (PR #3033 merged at 17:34, before the bus-envelope row was even committed). But: the bus-envelope row was shipped (PR #3043), so moving its ID would break the link from PR #3043 → backlog row + the ID is already referenced by B-0445 and B-0448's `composes_with`. Resolution: keep B-0444 on the shipped+referenced bus-envelope row; renumber the getting-started row to the next available ID (B-0450 — B-0449 was taken by Otto-Desktop's bg-services slice 5). Changes: - `docs/backlog/P1/B-0444-getting-started-guide-...md` → `docs/backlog/P1/B-0450-getting-started-guide-...md` (file rename via `git mv` preserves history); `id` field updated; body title updated; `renumbered_from: B-0444` + reason recorded for substrate-honest provenance. - `docs/backlog/P1/B-0271-pm2-first-research-pass-...md`: updated Gap-1 reference from B-0444 to B-0450 with renumber note. - `docs/backlog/P2/B-0444-bus-claim-envelope-...md`: status open → closed; `closed_at: 2026-05-13` + `closed_by_pr: 3043` recorded (this row's substrate work shipped already; the row was just never marked closed). - `docs/BACKLOG.md`: regenerated via `BACKLOG_WRITE_FORCE=1 bun tools/backlog/generate-index.ts` to reflect the renumber + status change. Verified post-fix: - `bun tools/bg/backlog-ready-notifier.ts --once` still reports 0 dangling-dep refs (no warning suffix). - No grep for `B-0444` in `docs/backlog/` returns the now-renumbered row's old ID; the bus-envelope row's references survive intact. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2306Z — B-0444 ID collision resolved + shipped bus-envelope row closed Records: PR #3050 merged; audit of in-flight rows surfaced TWO rows both with `id: B-0444` (one shipped via PR #3043 still open; one duplicate from PM-2 gap-prediction). PR #3053 keeps B-0444 on the shipped+referenced row, renumbers the duplicate to B-0450, closes the shipped row. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Implements B-0444 — adds
worktree?: stringtoClaimPayloadso the claim envelope captures the worktree path the claim was acquired from. Closes Otto-Desktop's identified follow-up gap filed in #3038.ClaimPayload.worktree?+ClaimRecord.worktree?(both optional → back-compat)acquire --worktree <path>flag; defaults toprocess.cwd()checktext +check --json+acquire --jsonall surface the fieldDesign decision (substrate-honest)
Surface-tagged sender IDs (#3037) arbitrate split-brain at the coordination layer (
otto-clivsotto-desktopare distinct claims on the same item).worktreeis observability metadata, not a coordination key — same sender re-acquiring from a different worktree remains idempotent. Pinned by the testsame sender re-acquiring from a different worktree is idempotent (existing behavior preserved).Test plan
tools/bus/claim.test.ts— default cwd, explicit--worktree, JSON shape, combined with--branch, surface-IDs-still-arbitrate, same-sender different-worktree idempotency, claim-record shapebun test tools/bus/claim.test.ts→ 52/52 pass/tmp/zeta-b0444to avoid multi-Otto split-brain (the very class of problem this row addresses)Composes with
.claude/rules/claim-acquire-before-worktree-work.md— rule this row's gap was identified against🤖 Generated with Claude Code