fix(tmux): register third_party/tmux as a real submodule, remove broken Bazel path#157
fix(tmux): register third_party/tmux as a real submodule, remove broken Bazel path#157tstapler wants to merge 27 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR does more than the stated tmux submodule fix: it also introduces a “stuck backlog items” feature end-to-end (proto/RPC/storage/UI/tests), adds a new backlog item flag (auto_spawn_session), and lands significant /unfinished scanner + git storage (mmap/index/watch) changes.
Changes:
- Re-registers
third_party/tmuxas a real git submodule and simplifiesmake build-tmuxto always usescripts/build-tmux.sh. - Adds “stuck backlog items” persistence + RPCs + frontend components/badges and accompanying unit/e2e tests.
- Introduces
/unfinishedscanner/gogitstore enhancements (fsnotify/event-driven scanning, mmap index feature flag, cache/eviction tests).
Reviewed changes
Copilot reviewed 94 out of 146 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web-app/src/lib/hooks/useStuckBacklogItems.ts | New polling hook for stuck backlog items + snooze |
| web-app/src/lib/hooks/useStuckBacklogItems.test.ts | Unit tests for the new hook |
| web-app/src/lib/hooks/useBacklogService.ts | Adds autoSpawnSession to backlog item models and mapping |
| web-app/src/components/layout/tests/Header.test.tsx | Mocks new StuckNavBadge in header tests |
| web-app/src/components/layout/tests/DrawerNav.test.tsx | Mocks new StuckNavBadge in drawer tests |
| web-app/src/components/layout/Header.tsx | Renders StuckNavBadge next to unfinished badge |
| web-app/src/components/layout/DrawerNav.tsx | Renders StuckNavBadge in drawer nav |
| web-app/src/components/backlog/TriageReviewPanel.test.tsx | Updates test item factory with autoSpawnSession |
| web-app/src/components/backlog/BacklogItemForm.tsx | Adds “Auto-spawn work session” checkbox + submits value |
| web-app/src/components/backlog/BacklogItemDetail.tsx | Prevents partial updates from resetting boolean flags |
| web-app/src/components/backlog/BacklogItemDetail.regression.test.tsx | Updates regression fixture with autoSpawnSession |
| web-app/src/components/backlog-stuck/stuckReason.ts | Adds stuck-reason labels/icons/classes + time formatting helpers |
| web-app/src/components/backlog-stuck/stuckReason.test.ts | Unit tests for stuckReason helpers |
| web-app/src/components/backlog-stuck/stuckReason.css.ts | Styles for stuck-reason chips |
| web-app/src/components/backlog-stuck/StuckNavBadge.tsx | New nav badge component for stuck backlog count |
| web-app/src/components/backlog-stuck/StuckNavBadge.test.tsx | Unit tests for nav badge states/accessibility |
| web-app/src/components/backlog-stuck/StuckNavBadge.css.ts | Styles for nav badge + skeleton |
| web-app/src/components/backlog-stuck/StuckItemsSection.css.ts | Styles for stuck items section UI (filters, banners, etc.) |
| web-app/src/components/backlog-stuck/StuckItemDetail.tsx | Detail panel rendering for a stuck item |
| web-app/src/components/backlog-stuck/StuckItemDetail.test.tsx | Unit tests for stuck item detail rendering |
| web-app/src/components/backlog-stuck/StuckItemDetail.css.ts | Styles for stuck item detail |
| web-app/src/components/backlog-stuck/StuckItem.css.ts | Styles for stuck item card + snooze affordance |
| web-app/src/app/unfinished/UnfinishedTab.tsx | Places StuckItemsSection on /unfinished |
| tools/scanner/backend/proto_scanner.go | Registers new backlog RPCs for feature registry IDs |
| third_party/tmux/BUILD.bazel | Removes Bazel/rules_foreign_cc build definition |
| third_party/tmux | Converts tmux into a git submodule gitlink |
| tests/e2e/pages/StuckItemsPage.ts | Adds Playwright page object + debug seeding helpers |
| tests/e2e/accessibility.spec.ts | Adds Axe contrast test for stuck-item chips |
| session/unfinished/scanner_eventdriven_test.go | Adds tests for fsnotify scanning and memory-pressure behavior |
| session/unfinished/gogitstore/storer.go | Introduces WorktreeStorer delegating object storage to shared store |
| session/unfinished/gogitstore/open.go | Adds worktree-aware go-git open logic with commondir sharing |
| session/unfinished/gogitstore/mmapwatch.go | Adds fsnotify watcher for pack-dir changes to refresh mmap indexes |
| session/unfinished/gogitstore/mmap_realrepo_test.go | Adds real-repo allocation delta measurement test |
| session/unfinished/gogit_vcs_reader_objectcache_test.go | Adds tests for per-repo cache sizing + shared registry reuse |
| session/unfinished/gogit_vcs_reader_mmap_flag_test.go | Adds tests for live-toggling mmap index feature flag |
| session/unfinished/gogit_vcs_reader_memory_test.go | Adds tests for memory-based repo cache eviction |
| session/unfinished/gogit_vcs_reader_gogitstore_eviction_test.go | Adds tests ensuring eviction releases store refcounts |
| session/stuck_decisions_test.go | Adds tests for new stuck decision predicates |
| session/stuck_decisions.go | Adds pure decision predicates for stuck detectors |
| session/storage.go | Adds Storage passthrough APIs for stuck-state access |
| session/review_gate.go | Adds base-commit auto-repair and explicit blocking on diff failure |
| session/repository.go | Adds AutoSpawnSession to backlog item data/update structs |
| session/git/worktree_ops.go | Fixes Cleanup to stop deleting branches; simplifies worktree cleanup |
| session/git/worktree_git.go | Extends PRStatus payload (isDraft/mergeable/review counts) |
| session/git/worktree_creation_test.go | Adds regression test ensuring cleanup preserves branches |
| session/git/worktree_branch.go | Removes now-unused combineErrors helper |
| session/ent/tx.go | Adds ent Tx client for BacklogStuckState |
| session/ent/schema/backlog_stuck_state.go | New ent schema for durable stuck state |
| session/ent/schema/backlog_item.go | Adds auto_spawn_session field + stuck_states edge |
| session/ent/runtime.go | Updates ent runtime defaults with new fields/schema |
| session/ent/predicate/predicate.go | Adds predicate type for BacklogStuckState |
| session/ent/migrate/schema.go | Adds schema table and FK/indexes for backlog_stuck_states + new column |
| session/ent/hook/hook.go | Adds hook adapter type for BacklogStuckState mutations |
| session/ent/ent.go | Registers backlogstuckstate in ent runtime validation |
| session/ent/backlogitem_query.go | Adds query + eager-loading support for stuck_states edge |
| session/ent/backlogitem_create.go | Adds create/upsert setters + edge linkage for stuck states + new field |
| session/ent/backlogitem/where.go | Adds predicates for auto_spawn_session + stuck_states edge |
| session/ent/backlogitem/backlogitem.go | Adds field/edge constants + ordering helpers + step for stuck_states |
| session/ent/backlogitem.go | Adds AutoSpawnSession scanning/assignment + stuck_states edge accessors |
| session/domain/backlog_test.go | Adds StuckReason validation tests |
| session/domain/backlog.go | Adds domain.StuckReason enum + validation |
| session/backlog_stuck_migration_test.go | Adds schema/constraint/cascade-delete/idempotency tests for stuck state |
| session/backlog_review.go | Adds RecoverBaseCommitSHA helper |
| server/services/feature_flag_service.go | Registers unfinished:mmap-index feature flag metadata |
| server/services/backlog_stuck_rpc_test.go | Adds RPC-level tests for list/snooze/mapping |
| server/services/backlog_service_stuck.go | Adds ListStuckBacklogItems + SnoozeStuckItem handlers |
| server/services/backlog_service_lifecycle.go | Adds stuck-resolution on manual transitions + auto_spawn propagation |
| server/services/backlog_service.go | Adds auto_spawn_session field in backlog proto mapping |
| server/services/backlog_debug_seed_handler.go | Adds e2e-local-only debug endpoint to seed stuck rows |
| server/services/autonomous_orchestration_service.go | Improves error surfacing when backlog status transition fails |
| server/server.go | Registers debug seed endpoint for e2e-local instance |
| server/dependencies.go | Adds stuck-state backfill and session-liveness checker wiring |
| scripts/build-tmux.sh | Fixes tarball fallback to extract additional autotools artifacts |
| proto/session/v1/backlog.proto | Adds auto_spawn_session + stuck backlog RPCs/messages/enums |
| project_plans/backlog-stuck-item-visibility/requirements.md | New requirements doc for the feature |
| project_plans/backlog-stuck-item-visibility/implementation/pre-mortem.md | New pre-mortem doc for the feature |
| go.mod | Adds mmap-go dependency |
| go.sum | Adds mmap-go and related sums |
| docs/registry/features/frontend/backlog-stuck-items.json | Registers frontend feature + tests |
| docs/registry/features/backend/backlog/snooze-stuck.json | Registers backend feature + tests |
| docs/registry/features/backend/backlog/list-stuck.json | Registers backend feature + tests |
| WORKSPACE | Removes Bazel workspace setup |
| Makefile | Removes Bazel tmux build path; fixes submodule prereq behavior |
| CLAUDE.md | Adds link to new SDD planning artifacts commit rule |
| .gitmodules | Removes submodule branch config |
| .gitignore | Removes stale ignore rules that hid tmux submodule contents |
| .claude/skills/backlog-feature-improvement/SKILL.md | Adds new internal audit skill doc |
| .claude/rules/sdd-planning-artifacts-commit.md | Adds new rule for committing SDD planning outputs |
Files not reviewed (2)
- gen/proto/go/session/v1/backlog.pb.go: Generated file
- gen/proto/go/session/v1/sessionv1connect/backlog.connect.go: Generated file
Comments suppressed due to low confidence (4)
session/storage.go:1
- The doc comment says this returns an empty slice (no error) when stuck-state queries aren’t supported, but the non-ent path returns a nil slice. For consistency with the comment (and to avoid callers needing to distinguish nil vs empty), return an empty slice (e.g.,
[]OpenStuckStateData{}) instead ofnil.
server/services/backlog_service_stuck.go:1 - When
s.storageis nil, ListStuckBacklogItems returns a successful empty list, which can silently mask a server misconfiguration/outage. Consider returningconnect.CodeUnavailable(matching SnoozeStuckItem’s behavior) or at least a non-OK response so clients don’t interpret this as 'no stuck items'.
session/review_gate.go:1 - A field named
SessionUUIDis being set to a non-UUID string (diff-error-<uuid>). If any downstream code assumes this is parseable as a UUID (common given the name), this will cause runtime failures or hard-to-debug behavior. Prefer using a valid UUID value inSessionUUID(e.g., plainuuid.NewString()), and encode the 'diff-error' condition in another field (e.g., verdict summary/details) rather than in the UUID itself.
server/services/backlog_debug_seed_handler.go:1 - For a test-only seeding endpoint, silently falling back to
time.Now()on an invalidfirstDetectedAtmakes e2e failures harder to diagnose (bad input won’t fail fast). Consider returning HTTP 400 whenfirstDetectedAtis present but fails RFC3339 parsing.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| # ── Pinned tmux binary (for test isolation) ──────────────────────────────── | ||
| # Builds tmux 3.4 from the third_party/tmux git submodule. | ||
| # Builds tmux 3.4 from the third_party/tmux git submodule via scripts/build-tmux.sh. |
| # order-only prerequisite on the submodule dir (not configure.ac): a fresh | ||
| # worktree/clone has an empty, uninitialized submodule directory, so a hard | ||
| # prerequisite on configure.ac would fail before build-tmux.sh's own | ||
| # gitlink-detection + init/clone-fallback logic ever runs. | ||
| $(TMUX_BUILD_STAMP): | third_party/tmux |
| rm -f "$$TMPTAR" && \ | ||
| echo "✅ configure downloaded"; \ | ||
| fi | ||
| @./scripts/build-tmux.sh |
third_party/tmux was committed as a plain tracked directory (only BUILD.bazel, mode 100644) instead of a gitlink (mode 160000), even though .gitmodules declared it as a submodule. This broke `git submodule update --init` / `make init-submodules` (git saw no gitlink to update) and, more importantly, broke the Makefile's prerequisite chain for the pinned tmux build on any fresh clone. Re-registered the gitlink pinned to the tmux 3.4 tag. Also dropped the stale `branch = 3.4` from .gitmodules — upstream tmux/tmux has no `3.4` branch, only a `3.4` tag (the original config would fail `git submodule add -b 3.4` outright). Nothing in this repo used `git submodule update --remote`, so the branch field was unused.
…sh worktrees
Bazel is unused elsewhere in this repo and its rules_foreign_cc setup
is permanently broken under Bazel 9 (WORKSPACE support removed in the
9.0 LTS bzlmod migration — confirmed via Bazel's own 9.0 announcement).
CI never exercised this path (.github/workflows/build.yml calls
scripts/build-tmux.sh directly). Collapsed build-tmux to always call
scripts/build-tmux.sh, dropping the `command -v bazel` branch and the
now-unused ensure-tmux-configure helper.
Also fixed a real bug: the stamp target had a hard prerequisite on
third_party/tmux/configure.ac, which fails immediately ("No rule to
make target") on any fresh clone/worktree, since `git worktree add`
does not auto-init submodules and the directory starts out empty.
That hard-failed before scripts/build-tmux.sh's own gitlink-detection
and init/clone-fallback logic ever got a chance to run. Switched to
an order-only prerequisite on the submodule directory so the recipe's
self-healing logic always runs.
A real git submodule checkout only has autoconf *source* (configure.ac, Makefile.am) — the release-tarball fast path previously extracted only the generated `configure` script, which then failed with "cannot find install-sh" and later "cannot find input file: Makefile.in". Release tarballs bundle these generated files (aclocal.m4, Makefile.in, and the etc/ scaffold: install-sh, config.guess/sub, missing, depcomp, compile) because they're produced by `automake`/`autoreconf` at dist time and were never committed to git. Extract them alongside configure. This was masked previously because third_party/tmux was populated by copying a full tarball (which includes these files) rather than a real git clone.
third_party/tmux/* and the !BUILD.bazel negation are stale now that third_party/tmux is a real gitlink — its contents belong to the submodule's own repo, not this one, and BUILD.bazel no longer exists. This ignore pattern (whitelisting only BUILD.bazel) is the likely original cause of the directory being committed as plain files instead of a submodule.
66ed24a to
a4e6c6c
Compare
📊 Feature E2E CoverageFeature coverage report unavailable
|
🎬 E2E Feature Demos2 shard(s) recorded feature flows for this PR. recordings shard 1 Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days. |
|
Closing — this fix has already landed on Root cause of the merge conflict: Verified The companion |
…eShot (#160) * fix(backlog): transition backlog item to pr_pending when PR is created via RunOneShot The Review Queue's manual "Create PR" button drives SessionService.RunOneShot, which creates a real PR but only ever persisted the PR URL onto the session record — it had no knowledge of backlog items at all. pushAndCreatePR (the automated review-PASS path) is the only other place that writes pr_pending, so any backlog item whose PR was created via the manual flow was silently left in "review" forever, invisible to ReconcilePRPending's FindPRPendingItems query. This is the root cause behind PR #157's linked backlog item being stuck at status review/BOUNCING instead of pr_pending. Add BacklogLifecycleListener.NotifyPRCreatedOutOfBand, called from RunOneShot after a PR URL is extracted, which stores the PR fields and transitions review -> pr_pending using the same precondition-guarded sequence as pushAndCreatePR. No-op when the listener is disabled, the session isn't backlog-linked, or the item isn't currently in review. 4 regression tests cover the transition, the non-backlog-session no-op, the non-review-status no-op, and the disabled-listener no-op. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * refactor(backlog): address code-review findings on RecordPRCreatedOutOfBand Four-agent parallel code review (testing, code quality, architecture, security) on the prior commit surfaced: - Testing: the resolveStuckLogged side effect (clearing a stale abandoned_review row) was never actually exercised by any of the 4 new tests. Added TestRecordPRCreatedOutOfBand_ClearsAbandonedReviewStuckReason_ WhenItemWasStuck, which marks an item stuck first and asserts the row is gone afterward. - Code quality: extracted the identical transition+resolve-stuck tail shared by pushAndCreatePR and the new method into resolveToPRPending, removing the duplication. Also renamed NotifyPRCreatedOutOfBand -> RecordPRCreatedOutOfBand — "Notify" collided in spirit with the existing l.notify() toast helper in this file, but this method mutates DB state, it doesn't just surface a message. - Code quality (concurrency): the doc comment overstated TransitionBacklogItemStatus's guarantee as "exactly one of two racing calls wins" — verified against session/ent_repository_backlog.go and it's actually a read-then-write with no WHERE-clause CAS, so both could succeed under a true race (harmless here since both write the same target state, but the comment was wrong). Corrected the wording. - Security: added workSessionUUID to the success log line so an operator can trace which session caused a given transition. Not fixed here (documented in the method's doc comment as known limitations, consistent with this codebase's existing pattern of tracking follow-ups rather than scope-creeping a bug fix): this path doesn't call EnablePRAutoMerge like pushAndCreatePR does (no worktree handle available), and prURL/prNumber come from unverified freeform LLM stdout parsing (extractPRURL) rather than a GitHub-verified check — acceptable for this single-operator tool's threat model today. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
#163) * fix(backlog): proactively sync PR branch with main before respawning a fix session AutoReopenForPRFix respawns an autonomous session whenever ReconcilePRPending detects a CI failure, blocking review, or merge conflict on a pr_pending item, but it only ever told the new session about the failure via a text note — it never actually resynced the branch with main first. A CI failure caused by drift from main (a fix landing on main after the PR's branch was created, or the branch simply going stale) wasted the respawned session's effort diagnosing something a merge would have fixed outright, and left the PR exposed to the same "developed a hard conflict with nobody proactively resyncing it" pattern that produced PR #157. AutoReopenForPRFix now merges main into the worktree behind the currently open PR (found via the most recent completed work ItemSession) before building the fix context: - a clean merge is pushed straight to the PR's branch on origin, so the live PR is resynced even before the new session starts; - a merge conflict is aborted immediately (via `git merge --abort`, leaving the worktree clean) and the conflicting file paths are folded into the fix context so resolving them against main becomes part of the fix; - an already-up-to-date branch is a silent no-op. Adds session/git.MergeMainIntoWorktree (fetch + merge + conflict abort/report, following the existing session/git/ops.go conventions) and BacklogService.syncPRBranchWithMain, wired in as a best-effort step ahead of the existing respawn — any sync failure is logged and swallowed, never blocking the fix spawn. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(backlog): address code review findings on PR branch-sync Code review on the branch-sync fix (PR #163) surfaced two MAJOR issues, now fixed, plus test-coverage gaps, now filled: - session/git/ops.go: MergeMainIntoWorktree detected an up-to-date merge by substring-matching git's merge output ("Already up to date"), which is locale- and git-version-fragile (older git prints "Already up-to-date."). Now compares HEAD before/after the merge via the package's existing getHeadCommitSHA helper instead. - backlog_service_triage.go: the push-failure note told the next fix session to "push it before continuing," but that session gets a brand-new worktree (SpawnSessionFromItem always creates one fresh on reopen), not the one the merge landed in — the instruction wasn't actionable from there. The note now names the branch and gives an explicit `git -C <repoPath> push origin <branch>` command against the shared repo checkout, whose branch ref survives worktree cleanup. - Added missing coverage: fetch failure and non-conflict merge failure in session/git/ops_test.go; push failure after a successful merge and a swallowed sync-fetch failure in backlog_service_triage_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* docs(backlog): record allow_auto_merge repo-setting root cause + status-desync bug Second, compounding root cause for "nothing merges": GitHub repo-level allow_auto_merge is disabled, so EnablePRAutoMerge's gh pr merge --auto call has been silently failing on every PR. Also notes the pr_pending status-desync bug blocking ReconcilePRPending for PR #157, and a fabricated subagent report incident from this audit pass. * docs(backlog): record CI-pending-as-green bug and PR-mergeability policy scope Confirmed via direct code read: GetPRStatus never distinguishes a still-running check from a passed one, so ReconcilePRPending's healthy-PR branch can fire the ready-to-merge notification before CI finishes. Also records the backlog-pr-mergeability-policy SDD scoping outcome and an open design tension between repo-level auto-merge (enabled this session) and the plan's per-item-gated auto-merge design. * docs(backlog): record autonomy-gap audit — orphaned_triage and bouncing don't self-heal Systematic pass over all 7 StuckReasons: only pr_pending auto-recovers. orphaned_triage's code comment falsely claims self-healing it doesn't actually do. bouncing has no escalation path. Also confirms the MCP controller-startup issue from the earlier audit doesn't affect backlog-spawned sessions, which are fully wired synchronously. * docs(backlog): record PR #168's respawn-gate caveat for pre-existing stuck rows Live-verified via the deployed reconciliation sweep: the abandoned-review auto-respawn correctly fires exactly once for newly-notified rows, but rows already marked notified before the fix deployed never trigger a respawn since it shares the same notify-once gate. Correct going forward, just doesn't retroactively catch pre-existing stuck items. * fix(test): share one tmux-socket reaper across session, session/mux, session/tmux session/integration_test.go's TestMain reaper only recognized "test_coldrestore_" socket names, silently missing "test-isolated-" (the name testSocketOnce in session/tmux generates and every package's tests share). session/mux had no reaper at all. A SIGKILLed test binary in either package left its tmux server running indefinitely — confirmed live in production as 4 multi-day-old orphaned "tmux: server" processes eating memory and holding stale worktree paths. Consolidate into testutil/tmuxreap, a dependency-free leaf package (unlike testutil itself, which already imports session/session-mux/session-tmux and so can't be imported by their internal-package test files without a cycle). All three packages' TestMain now call the same ReapLeakedTestServers / StartTestServerWatchdog, covering the full set of test socket prefixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * feat(scripts): add sync-worktrees to merge main into every worktree Ad-hoc merges into ~40 backlog/agent worktrees (done manually this session to land the tmux-reaper fix) showed the gap plainly: worktrees silently drift behind main until someone remembers to merge by hand, so fixes on main (test hygiene, lint rules, bugfixes) don't reach in-progress or idle backlog worktrees on their own. `make sync-worktrees` merges main into every worktree, skips any with uncommitted changes, and aborts + reports (without forcing a resolution) any real conflict for manual follow-up. Safe to re-run — a no-op once everything's current. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * feat(backlog): tell the agent to sync with main via the existing context file Considered a Go-side periodic reconciler (session/git.MergeMainIntoWorktree already exists and is used by AutoReopenForPRFix's syncPRBranchWithMain) but that only helps a narrow case — SpawnSessionFromItem always branches a fresh worktree off current main on every real reopen, so external auto-merge would mostly be a no-op and would need its own "is this worktree actively in use" safety check to avoid yanking the tree out from under a running agent. Simpler and safer: WriteBacklogContextFile already regenerates .backlog-context.md on every spawn AND every re-attach (see AttachSessionToItem in backlog_service_sync.go) — the harness's existing state-management touchpoint the agent reads as its briefing. Telling the agent to merge main as its own first step is inherently safe (it's the one about to use the worktree) and it can actually resolve a trivial conflict, where a background script could only abort and flag it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * fix(backlog): require code verified on main, not just PrURL set, before done An item could reach "done" once PrURL existed at all — an open, unmerged, or later-reverted PR still has PrURL set, so it always satisfied the old guard. "Approval" (a PASS review verdict) and "shipped" (code actually on main) were being conflated: a PASS verdict says the code is good, not that it landed. Three separate call sites could drive a review→done transition, and only one (the TransitionBacklogItemStatus RPC) had any shipped-code guard at all — TriggerReReview's and SubmitManualReview's auto-transition-on-PASS bypassed it entirely by calling storage.TransitionBacklogItemStatus directly. All three now route through the same isCodeShippedToMain check. "Shipped" means the most recent work session's commit is an ancestor of main — checked locally AND via origin (a PR merged remotely on GitHub doesn't require a local pull to count; a commit merged directly to main locally never went through a PR at all). Implemented as git.IsCommitOnMain using go-git (no subshell — see the new prefer-go-git-over-subshells rule), with an origin fetch that's best-effort so an offline/unreachable remote doesn't block the local-merge case. Verification failure fails closed: if the commit can't be confirmed shipped (or the check errors), the transition is blocked rather than silently trusting a stale PrURL. The RPC path keeps its override_reason escape hatch; the two internal auto-transition paths have none by design — they leave the item in review for a human to decide via the RPC path instead. ErrPRRequired renamed to ErrCodeNotOnMain to match what it actually checks now; no external callers referenced the old name (verified via repo-wide grep, zero test references either). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * docs(backlog): record merged-before-done gate fix + zero-findings audit Documents the isCodeShippedToMain fix (PrURL alone no longer proves shipped; all 3 done-transition call sites now gated) and the resulting audit of all 11 current status=done items: 4 confirmed MERGED via gh, 7 confirmed on main via git ancestry, zero improperly closed. Also re-notes the live DB path trap (workspaces/d685c4b1a423cca3/sessions.db, not the top-level ~/.stapler-squad/sessions.db) that the existing "Skill Fix Needed" entry already flagged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * feat(backlog): add durable ship-status widget for done items The Version Control section in BacklogItemDetail only ever showed live VCSStatus, fetched via a live in-memory session Instance (findInstanceFast). Once a done item's work session is stopped and its worktree cleaned up — the normal end state — there's no Instance left to query, so the widget silently rendered nothing. Not a bug exactly (there's no live "now" left to show for a deleted directory), but a real gap: no way to see how an item shipped after the fact. Adds GetBacklogItemShipStatus, a new read-only RPC answering "did this actually ship" from durable evidence instead: repo_path + the most recent work session's commit. Reuses this session's IsCommitOnMain (shipped locally or via a merged PR) and adds git.BranchAheadBehind (go-git, no subshell) to show the branch's position relative to main when it still exists, or "(deleted — already merged)" once it's been cleaned up post-ship. Frontend: useBacklogItemShipStatus + ShipStatusDisplay render as the fallback exactly when the live vcsStatus comes back empty, so active sessions keep seeing live git status and done items get the historical "shipped via PR #N" / "shipped directly to main" badge instead. Also fixes the backend feature-registry scanner: GetBacklogItemShipStatus wasn't in proto_scanner.go's methodToID table, so it fell back to the raw method name as its feature ID and never matched its own +api: marker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * feat(backlog): richer ship-status widget — commit list + working diff view Two real gaps in "what happened to this item's code," both closed with durable-git-history evidence rather than a live session: 1. GetBacklogItemDiff fell back to diffing whatever's currently checked out in item.RepoPath once a work session's worktree directory was gone (the normal state for a done item) — silently wrong or empty, not what the branch actually shipped. GetGitDiffRef already existed for exactly this ("worktrees share the same object store, so any ref reachable from any worktree resolves correctly regardless of dir") but wasn't being used here. Now always diffs from item.RepoPath with an explicit headRef, so it works identically whether the worktree still exists or not. 2. Tyler: "are we able to identify which commits were shipped to main from the branch so I can see that diff, kind of like we link to the PR" — adds git.ListShippedCommits (base..head ancestry walk, same pattern as BranchAheadBehind) and wires it into GetBacklogItemShipStatus as ShipStatusDisplay's commit list (newest first, like a PR's Commits tab), plus a "View Diff" button that opens the same ReviewChangesModal already used by the review flow — now unconditionally, not gated to review status, since the diff fix above makes it work for any item regardless of status. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XXewUpVzfJEKJoKMg99JNN * fix(unfinished): remove stale review-queue entries when a worktree goes clean Deleting the only uncommitted file in a worktree left a stale entry in the Unfinished Work scanner's resultStore since nothing ever cleared it once the worktree scanned clean again. Mirrors the existing CleanWorktree removal pattern from review_queue_poller.go/ review_queue_determiner.go instead of inventing a new mechanism. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NC245UX8ukDqzudPNZcQtu * chore(backlog): sync harness scaffold for item 96cc9eaa Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NC245UX8ukDqzudPNZcQtu * chore(backlog): untrack backlog context/command files These match .gitignore rules already but were tracked from before the rule was added, so gitignore had no effect and automation kept committing per-worktree state to them. git rm --cached only; local copies are untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqpFYpiUbPMTnZrNW4grg7 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ssing A rework/reopen cycle can leave a session's stored worktree path pointing at a directory that no longer exists (recreated elsewhere, or removed by a concurrent cleanup). IsDirty() had no error-caching path, so a broken worktree got re-checked (and re-spawned a git subprocess) on every poller tick indefinitely — 439 failed spawns in 20 minutes observed live on one session. Cache the failure with a 60s backoff, same mechanism already used for dirty/clean results.
Added to the generated unit: - MemoryHigh/MemoryMax (60%/80% of detected system RAM) so a runaway burst of concurrent agent sessions is contained to this service's cgroup instead of taking down the whole box, the way the 2026-07-12 OOM incident did. - A mild OOMScoreAdjust=-500 nudging survival odds toward the coordinator. - StartLimitIntervalSec/Burst raised so systemd doesn't permanently disable the unit after a burst of OOM-kill/restart cycles during sustained pressure (default 5-restarts/10s can trip in one bad episode).
…ion audit trail Resilience fixes (2026-07-12 OOM follow-up): - WIP cap now counts live work sessions across both in_progress and review status. AutoReopenAfterFailedReview intentionally leaves a work session alive polling for a verdict once an item flips back to "review" — the old in_progress-only count made those sessions invisible to the concurrent agent limit. - Cold-restore now re-detects the Claude conversation UUID immediately via the live process's open files instead of leaving it blank until some unrelated caller triggers detection later. A second crash/restart in that window previously lost conversation resumability entirely. - Headless triage sessions get a 30-minute staleness threshold instead of the general 2h one — an open headless-triage row reliably means dead, not slow, so operators were waiting up to 2h for a notification. - New reconcileUnprocessedReviewVerdicts detector: a review session can die (crash/OOM/restart) after recording its verdict but before its exit event reaches the code that acts on it (push+PR on PASS, auto-reopen otherwise). The existing zombie detector only fires when EVERY session on the item is confirmed dead, but AutoReopenAfterFailedReview's live-session-reuse keeps a work session open polling for the verdict — so the item never looked like a zombie even though the review side was the one that died. Found live: a work session correctly determined its item's PASS verdict was already recorded with nothing left to build, but had no way to force the review->done transition itself, and looped forever re-requesting a review the backlog system correctly rejected. Audit trail (the data behind the above was already being recorded but never reached the UI): - BacklogStatusEvent.note (the human-readable reason for a transition, e.g. "auto-reopened after FAIL verdict") is now exposed over the wire and shown in the item's Workflow timeline. - New BacklogProgressNote proto message + GetBacklogItem eager-load surfaces the implementer's full report_progress call history as a "Progress History" section. - Review verdicts (already wired to the frontend, never rendered) now show per-criterion outcome + evidence + summary under each session. - The triggeredBy actor is now always shown, not just when it's "user".
Two test fixture builders were missed when the progressNotes field was added to the BacklogItem domain type in the audit-log work — left them failing tsc's structural check.
…isibility
Rework-cap override:
- New nilable BacklogItem.rework_cap_override column (nil = global default,
0 = unlimited for this item, >0 = this item's own cap), threaded through
the repository DTOs, proto, and UpdateBacklogItem RPC.
- The three automatic-retry call sites (AutoReopenAfterFailedReview,
AutoReopenForPRFix, AutoRespawnReview) now read effectiveReworkCap(item)
instead of only the global default.
- The Unfinished tab's rework_cap card gets a real inline control — set a
new cap or remove it entirely, which immediately reopens the item —
replacing static guidance text that only pointed at buttons elsewhere.
Autonomous turn-cap visibility ("conversions limit"):
- New StuckReasonAutonomousStuck / STUCK_REASON_AUTONOMOUS_STUCK. When an
autonomous driver run stops without a DONE signal, onAutonomousDriverComplete
now writes a durable BacklogStuckState row (additive to the existing
ephemeral notification), so a turn-cap stop shows up in the Unfinished tab
like every other stuck reason instead of only reaching the Alerts feed.
- Treated as event-shaped (resolved only via an explicit action, not a
status anchor) because a stuck work-role run still transitions
in_progress->review regardless of outcome — anchoring on in_progress would
false-resolve the row before an operator ever saw it. That existing
transition behavior is unchanged; flagged in comments as a separate,
not-yet-made decision.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ
TransitionGuard only matched from==review, so a pr_pending item could reach done via a bare TransitionBacklogItemStatus call with no verdict or shipped-code check at all. Found live: a real backlog item reached done while its GitHub PR was still open with merge conflicts, permanently orphaning that PR from ReconcilePRPending's monitoring (which only polls pr_pending-status items) — most likely a manual "Approve" click that didn't notice a fresh conflict had just been surfaced. Widens the guard to `to == done`, the only two edges (review->done and pr_pending->done) that reach it in validTransitions. The automated ReconcilePRPending path that legitimately drives pr_pending->done already verifies IsPRMerged() itself first, so its behavior is unchanged — this only closes the gap for every other caller.
… call CreatePR reused an existing PR's URL correctly but derived its number from a separate `gh pr view --head` subprocess call whose failure was silently swallowed, leaving prNumber at 0. That 0 then flowed into EnablePRAutoMerge(0), which predictably failed with "no pull requests found" — so auto-merge silently never got enabled for a PR that otherwise pushed and tracked correctly. Parses the number directly from the URL (a plain string op that can't fail the way a subprocess can) first, falling back to the gh-view lookup only if the URL doesn't parse.
3 was tripping routinely on real, ultimately-fixable items (a flaky diff/review harness needing a few extra passes, a straightforward merge conflict) well before the work was actually stuck, forcing manual "Reopen for Revision" clicks for otherwise-recoverable items. Genuinely stuck items still get caught, just later. Per-item overrides (BacklogItemData. ReworkCapOverride, added earlier) remain available for items that need to go further still.
…Unknown Session" placeholder tmux session creation was failing with a bare "exit status 1" across multiple backlog items with no way to diagnose why. Route stderr to a scratch file (not a buffer/pipe — a detached tmux server inherits and holds the fd open, so a buffer-based read blocks forever) so the real tmux error surfaces in the returned error. Separately, sessionless notification events (e.g. notifyReworkCapHit) were defaulting sessionName to "Unknown Session", which won the NotificationPanel/Toast fallback chain over the notification's own real title. Default to empty string instead so the real title shows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ
…x stderr test The stderr capture landed in the prior commit surfaced the real tmux failure across every backlog auto-fix/review spawn: "command too long". install-service.sh embeds "$PATH:<fallbacks>" verbatim into the systemd unit / LaunchAgent on every install, with no dedup — repeated installs from a shell whose own PATH already carries duplicates (e.g. nested tool/plugin PATH prepends) compound further each time, since the next install's shell inherits the bloated unit-derived PATH. Here that reached 360 entries / ~11.8KB, and re-embedding it via `-e PATH=...` in every spawned tmux session pushed the total `new-session` command past tmux's message-size limit, breaking session creation project-wide. dedup_path collapses it back to first-occurrence order (360 entries / 11.8KB -> 46 / 1.4KB here); also removes a duplicated copy-pasted PATH block in install_linux. Also add TestStartTmuxSession_IncludesTmuxStderrOnFailure covering the stderr-capture change from the prior commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ
…-bazel # Conflicts: # .backlog-context.md # .claude/commands/backlog/done-0.md # .claude/commands/backlog/done-1.md # .claude/commands/backlog/done-2.md # .claude/commands/backlog/fail-0.md # .claude/commands/backlog/fail-1.md # .claude/commands/backlog/fail-2.md # .claude/commands/backlog/help.md # .claude/commands/backlog/review.md # .claude/commands/backlog/status.md # .gitignore # Makefile # scripts/build-tmux.sh
…-fix-tmux-submodule-and-bazel
* docs(jules): analyze Jules feature overlap, close plan-gate test/registry gaps Jules and stapler-squad both supervise an AI coding agent through a plan approve → execute → review loop, so most of what makes Jules good already has a home in an existing backlog primitive here. docs/jules-feature-adoption.md lists what's worth building next (PR-summary card, activity feed, auto-fix-on-CI-failure loop, GitHubIssueDetector, concurrency throttle), ranked by leverage and scoped to extend existing services. The flagship recommendation — a plan-before-execute gate — turned out to already be fully implemented (BacklogItem.plan_approved, TransitionGuard, ApprovePlan RPC, GateVerdictBox/Approve Plan UI) but had no e2e coverage and a stale feature-registry entry (tested: false despite passing Go tests). Added tests/e2e/plan-gate.spec.ts to cover the gate/skip-planning path and corrected the backend/frontend registry entries to reflect existing coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * [claudesquad] save work before done (session f5b7f803-a5b6-4b92-923c-112b9c8229c0) * docs(jules): tighten container-sandboxing guardrail wording; remove stray scanner binary Sharpen the deferred-work section to explicitly name the container-per-session (Docker/Podman) ADR criterion: it stays unscoped until a concrete, measured local-resource-contention incident is on record, not just a hypothetical one. Also drops tools/scanner/backend/cmd/scanner, an empty build artifact that got tracked by an earlier bad .gitignore edit in this branch's history. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012LPVbTCHNhUt6BKnUtTfCs * chore(deps): update go.sum checksums picked up by local build --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(backlog): gate MCP tools and settings-sources page behind the backlog flag The "backlog" feature flag already defaulted off and gated ConnectRPC calls and nav visibility (66b1d83), but two surfaces still bypassed it: - server/mcp/server.go registered backlog/goal MCP tools via a dead `storage != nil` check, so an agent session could read/mutate backlog data over MCP even with the flag off. Added a live `enabledCheck func() bool` to backlogHandlers/goalHandlers (checked on every call, so a runtime toggle takes effect without restarting the MCP server) and gated registration itself at startup. Threaded backlogCtrl.IsEnabled through the HTTP transport and cfg.GetFeatureFlag through the stdio transport. - web-app/src/app/settings/backlog-sources had no feature-flag guard and was reachable by direct URL when disabled. Added a layout.tsx redirect guard matching backlog/layout.tsx, and hid its settings-page nav link when the flag is off. Also reconciled three stale StatusStable badges in server/features/backlog.go to StatusExperimental to match the hidden-by-default framing. Backlog item: df0d5872-9da4-4498-8a31-da355fb6ee07 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(backlog): update worktree bootstrap context for this item Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(backlog): update worktree bootstrap context for this item Backlog item: df0d5872-9da4-4498-8a31-da355fb6ee07 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(git): back off git status retries when a worktree directory is missing A rework/reopen cycle can leave a session's stored worktree path pointing at a directory that no longer exists (recreated elsewhere, or removed by a concurrent cleanup). IsDirty() had no error-caching path, so a broken worktree got re-checked (and re-spawned a git subprocess) on every poller tick indefinitely — 439 failed spawns in 20 minutes observed live on one session. Cache the failure with a 60s backoff, same mechanism already used for dirty/clean results. * chore(systemd): bound service memory and tolerate OOM-restart bursts Added to the generated unit: - MemoryHigh/MemoryMax (60%/80% of detected system RAM) so a runaway burst of concurrent agent sessions is contained to this service's cgroup instead of taking down the whole box, the way the 2026-07-12 OOM incident did. - A mild OOMScoreAdjust=-500 nudging survival odds toward the coordinator. - StartLimitIntervalSec/Burst raised so systemd doesn't permanently disable the unit after a burst of OOM-kill/restart cycles during sustained pressure (default 5-restarts/10s can trip in one bad episode). * fix(backlog): close review-cycle stuck/resilience gaps, surface decision audit trail Resilience fixes (2026-07-12 OOM follow-up): - WIP cap now counts live work sessions across both in_progress and review status. AutoReopenAfterFailedReview intentionally leaves a work session alive polling for a verdict once an item flips back to "review" — the old in_progress-only count made those sessions invisible to the concurrent agent limit. - Cold-restore now re-detects the Claude conversation UUID immediately via the live process's open files instead of leaving it blank until some unrelated caller triggers detection later. A second crash/restart in that window previously lost conversation resumability entirely. - Headless triage sessions get a 30-minute staleness threshold instead of the general 2h one — an open headless-triage row reliably means dead, not slow, so operators were waiting up to 2h for a notification. - New reconcileUnprocessedReviewVerdicts detector: a review session can die (crash/OOM/restart) after recording its verdict but before its exit event reaches the code that acts on it (push+PR on PASS, auto-reopen otherwise). The existing zombie detector only fires when EVERY session on the item is confirmed dead, but AutoReopenAfterFailedReview's live-session-reuse keeps a work session open polling for the verdict — so the item never looked like a zombie even though the review side was the one that died. Found live: a work session correctly determined its item's PASS verdict was already recorded with nothing left to build, but had no way to force the review->done transition itself, and looped forever re-requesting a review the backlog system correctly rejected. Audit trail (the data behind the above was already being recorded but never reached the UI): - BacklogStatusEvent.note (the human-readable reason for a transition, e.g. "auto-reopened after FAIL verdict") is now exposed over the wire and shown in the item's Workflow timeline. - New BacklogProgressNote proto message + GetBacklogItem eager-load surfaces the implementer's full report_progress call history as a "Progress History" section. - Review verdicts (already wired to the frontend, never rendered) now show per-criterion outcome + evidence + summary under each session. - The triggeredBy actor is now always shown, not just when it's "user". * fix(web): backfill progressNotes in backlog test fixtures Two test fixture builders were missed when the progressNotes field was added to the BacklogItem domain type in the audit-log work — left them failing tsc's structural check. * feat(backlog): per-item rework-cap override and autonomous turn-cap visibility Rework-cap override: - New nilable BacklogItem.rework_cap_override column (nil = global default, 0 = unlimited for this item, >0 = this item's own cap), threaded through the repository DTOs, proto, and UpdateBacklogItem RPC. - The three automatic-retry call sites (AutoReopenAfterFailedReview, AutoReopenForPRFix, AutoRespawnReview) now read effectiveReworkCap(item) instead of only the global default. - The Unfinished tab's rework_cap card gets a real inline control — set a new cap or remove it entirely, which immediately reopens the item — replacing static guidance text that only pointed at buttons elsewhere. Autonomous turn-cap visibility ("conversions limit"): - New StuckReasonAutonomousStuck / STUCK_REASON_AUTONOMOUS_STUCK. When an autonomous driver run stops without a DONE signal, onAutonomousDriverComplete now writes a durable BacklogStuckState row (additive to the existing ephemeral notification), so a turn-cap stop shows up in the Unfinished tab like every other stuck reason instead of only reaching the Alerts feed. - Treated as event-shaped (resolved only via an explicit action, not a status anchor) because a stuck work-role run still transitions in_progress->review regardless of outcome — anchoring on in_progress would false-resolve the row before an operator ever saw it. That existing transition behavior is unchanged; flagged in comments as a separate, not-yet-made decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ * fix(backlog): guard pr_pending->done the same way as review->done TransitionGuard only matched from==review, so a pr_pending item could reach done via a bare TransitionBacklogItemStatus call with no verdict or shipped-code check at all. Found live: a real backlog item reached done while its GitHub PR was still open with merge conflicts, permanently orphaning that PR from ReconcilePRPending's monitoring (which only polls pr_pending-status items) — most likely a manual "Approve" click that didn't notice a fresh conflict had just been surfaced. Widens the guard to `to == done`, the only two edges (review->done and pr_pending->done) that reach it in validTransitions. The automated ReconcilePRPending path that legitimately drives pr_pending->done already verifies IsPRMerged() itself first, so its behavior is unchanged — this only closes the gap for every other caller. * fix(git): resolve PR number from the URL instead of a second flaky gh call CreatePR reused an existing PR's URL correctly but derived its number from a separate `gh pr view --head` subprocess call whose failure was silently swallowed, leaving prNumber at 0. That 0 then flowed into EnablePRAutoMerge(0), which predictably failed with "no pull requests found" — so auto-merge silently never got enabled for a PR that otherwise pushed and tracked correctly. Parses the number directly from the URL (a plain string op that can't fail the way a subprocess can) first, falling back to the gh-view lookup only if the URL doesn't parse. * chore(backlog): raise default rework cap from 3 to 20 3 was tripping routinely on real, ultimately-fixable items (a flaky diff/review harness needing a few extra passes, a straightforward merge conflict) well before the work was actually stuck, forcing manual "Reopen for Revision" clicks for otherwise-recoverable items. Genuinely stuck items still get caught, just later. Per-item overrides (BacklogItemData. ReworkCapOverride, added earlier) remain available for items that need to go further still. * fix(backlog): apply RequireFeatureFlag guard directly to backlog-sources page.tsx Previous review flagged AC1 as PARTIAL: the guard existed only in a sibling layout.tsx, not on page.tsx as the acceptance criterion specifies. Wraps the page's own render output in RequireFeatureFlag so it can never render gated content even outside the layout tree. * feat(backlog): add RequireFeatureFlag guard component; refresh worktree bootstrap context RequireFeatureFlag wraps page render output behind a feature flag check via useFeatureFlags(), for Server Component pages that need a client-side gate without losing their static metadata export. Also refreshes the backlog worktree bootstrap files (.backlog-context.md, backlog/review command) for this item's rerun. * fix(tmux,notifications): capture tmux stderr on spawn failure; drop "Unknown Session" placeholder tmux session creation was failing with a bare "exit status 1" across multiple backlog items with no way to diagnose why. Route stderr to a scratch file (not a buffer/pipe — a detached tmux server inherits and holds the fd open, so a buffer-based read blocks forever) so the real tmux error surfaces in the returned error. Separately, sessionless notification events (e.g. notifyReworkCapHit) were defaulting sessionName to "Unknown Session", which won the NotificationPanel/Toast fallback chain over the notification's own real title. Default to empty string instead so the real title shows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ * fix(service): dedup PATH entries baked into the service unit; add tmux stderr test The stderr capture landed in the prior commit surfaced the real tmux failure across every backlog auto-fix/review spawn: "command too long". install-service.sh embeds "$PATH:<fallbacks>" verbatim into the systemd unit / LaunchAgent on every install, with no dedup — repeated installs from a shell whose own PATH already carries duplicates (e.g. nested tool/plugin PATH prepends) compound further each time, since the next install's shell inherits the bloated unit-derived PATH. Here that reached 360 entries / ~11.8KB, and re-embedding it via `-e PATH=...` in every spawned tmux session pushed the total `new-session` command past tmux's message-size limit, breaking session creation project-wide. dedup_path collapses it back to first-occurrence order (360 entries / 11.8KB -> 46 / 1.4KB here); also removes a duplicated copy-pasted PATH block in install_linux. Also add TestStartTmuxSession_IncludesTmuxStderrOnFailure covering the stderr-capture change from the prior commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJx6jAdjfXRVV1uJtkaeZ --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
✅ Registry ValidationTest Coverage: 12/174 features have
|
UX Analysis
|
Go Benchmarks (Tier 1) |
E2E RPC Latency |
Frontend Terminal Throughput |
…-fix-tmux-submodule-and-bazel
… signal IsNamedInstance()/IsTestMode() didn't cover the demo/test-mode harness (tests/demo/helpers.go StartDemoServer), which launches the real binary with --test-mode --test-dir — setting STAPLER_SQUAD_TEST_DIR but neither STAPLER_SQUAD_INSTANCE nor a `go test` binary suffix. That process gets a fully isolated DB but its startup orphan sweep still targeted the shared default tmux socket, killing every real production session it didn't recognize. New IsIsolatedInstance() covers all three known isolation mechanisms; ReconcileOrphanedTmuxSessions's caller guard now uses it.
…check Pre-existing exec.Command call sites in session/unfinished/gogitstore test files failed the custom norawexec lint pass, which was the last remaining blocker to make quick-check succeeding end-to-end after the tmux submodule and Makefile prerequisite fixes. Swapped to safeexec.CommandContext with context.Background() — behavior is unchanged since none of these call sites set a deadline (WaitDelay only matters on context cancellation). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012mcFRAXBv5jmfTQ4ip5PHB
Summary
third_party/tmuxas a real git submodule gitlink (mode 160000, pinned to tmux tag 3.4) — it was previously a plain tracked directory containing onlyBUILD.bazel.rules_foreign_ccbuild path (incompatible with Bazel 9's mandatory bzlmod migration, unused by CI) —make build-tmuxnow always usesscripts/build-tmux.sh.third_party/tmux/configure.ac, which failed with "No rule to make target" on any fresh clone/worktree before the script's self-healing init/clone logic ever got a chance to run.scripts/build-tmux.shonly extractedconfigure, butconfigurealso needsaclocal.m4,Makefile.in, and theetc/autotools scaffold (install-sh, config.guess/sub, missing, depcomp, compile) — now extracted too..gitignorerules forthird_party/tmux/*(the likely original cause of the corruption).Test plan
git ls-files --stage third_party/tmuxshows mode160000git worktree add(not this branch's worktree): submodule starts uninitialized,make init-submodulespopulates itmake build-tmux(no Bazel) succeeds,bin/tmux -Vreportstmux 3.4go test -short -cover ./...passes against the freshly-built pinned tmux binary (one pre-existing, environment-specific test failure unrelated to this change — see PR discussion).github/workflows/build.ymlstill passes unmodified🤖 Generated with Claude Code