Skip to content

fix(tmux): register third_party/tmux as a real submodule, remove broken Bazel path#157

Open
tstapler wants to merge 27 commits into
mainfrom
backlog/stapler-squad-fix-tmux-submodule-and-bazel
Open

fix(tmux): register third_party/tmux as a real submodule, remove broken Bazel path#157
tstapler wants to merge 27 commits into
mainfrom
backlog/stapler-squad-fix-tmux-submodule-and-bazel

Conversation

@tstapler

Copy link
Copy Markdown
Owner

Summary

  • Re-registers third_party/tmux as a real git submodule gitlink (mode 160000, pinned to tmux tag 3.4) — it was previously a plain tracked directory containing only BUILD.bazel.
  • Removes the broken Bazel/rules_foreign_cc build path (incompatible with Bazel 9's mandatory bzlmod migration, unused by CI) — make build-tmux now always uses scripts/build-tmux.sh.
  • Fixes the Makefile's hard prerequisite on 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.
  • Fixes a real bug uncovered by testing against a genuine git checkout: the release-tarball fallback in scripts/build-tmux.sh only extracted configure, but configure also needs aclocal.m4, Makefile.in, and the etc/ autotools scaffold (install-sh, config.guess/sub, missing, depcomp, compile) — now extracted too.
  • Drops stale .gitignore rules for third_party/tmux/* (the likely original cause of the corruption).

Test plan

  • git ls-files --stage third_party/tmux shows mode 160000
  • Verified in a genuine fresh git worktree add (not this branch's worktree): submodule starts uninitialized, make init-submodules populates it
  • Verified in a second genuine fresh/cold worktree: make build-tmux (no Bazel) succeeds, bin/tmux -V reports tmux 3.4
  • go 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)
  • CI (this PR) confirms .github/workflows/build.yml still passes unmodified

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 15, 2026 20:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/tmux as a real git submodule and simplifies make build-tmux to always use scripts/build-tmux.sh.
  • Adds “stuck backlog items” persistence + RPCs + frontend components/badges and accompanying unit/e2e tests.
  • Introduces /unfinished scanner/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 of nil.
    server/services/backlog_service_stuck.go:1
  • When s.storage is nil, ListStuckBacklogItems returns a successful empty list, which can silently mask a server misconfiguration/outage. Consider returning connect.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 SessionUUID is 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 in SessionUUID (e.g., plain uuid.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 invalid firstDetectedAt makes e2e failures harder to diagnose (bad input won’t fail fast). Consider returning HTTP 400 when firstDetectedAt is present but fails RFC3339 parsing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile

# ── 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.
Comment thread Makefile Outdated
Comment on lines +241 to +245
# 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
Comment thread Makefile
rm -f "$$TMPTAR" && \
echo "✅ configure downloaded"; \
fi
@./scripts/build-tmux.sh
tstapler added 4 commits July 15, 2026 13:39
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.
@tstapler
tstapler force-pushed the backlog/stapler-squad-fix-tmux-submodule-and-bazel branch from 66ed24a to a4e6c6c Compare July 15, 2026 20:39
@github-actions

Copy link
Copy Markdown
Contributor

📊 Feature E2E Coverage

Feature coverage report unavailable

Run make e2e-report locally to view the full Allure report.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🎬 E2E Feature Demos

2 shard(s) recorded feature flows for this PR.

recordings shard 1
recordings shard 2

Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days.

@tstapler

Copy link
Copy Markdown
Owner Author

Closing — this fix has already landed on main via a separate, independent path.

Root cause of the merge conflict: main received an equivalent fix for the exact same underlying bug (broken third_party/tmux submodule registration + dead Bazel/rules_foreign_cc path) through two direct commits, 7058d92f (re-register third_party/tmux as a real gitlink) and c4adbbfc (remove the broken Bazel path + fix the Makefile stamp prerequisite for fresh worktrees), plus an earlier c9a2528c (extract the full tmux release tarball). Both this PR's branch and main independently edited .gitmodules, WORKSPACE, Makefile, .gitignore, and the third_party/tmux gitlink to solve the same problem, which is why the merge shows as CONFLICTING — not because of any remaining incompatibility, but because the same fix was made twice on divergent branches.

Verified main already has the fix: third_party/tmux is a real gitlink (mode 160000), WORKSPACE is gone, and running make build-tmux from a fresh/uninitialized worktree (submodule not yet checked out) self-heals and builds tmux 3.4 successfully — the exact failure mode this PR was written to fix.

The companion backlog/stapler-squad-fix-tmux-submodule-and-bazel-r2 branch was a second attempt at the same fix; it never had an open PR, so no further action needed there. No code changes required — the bug is already fixed on main.

@tstapler tstapler closed this Jul 17, 2026
tstapler added a commit that referenced this pull request Jul 17, 2026
…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>
tstapler added a commit that referenced this pull request Jul 17, 2026
#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>
tstapler added a commit that referenced this pull request Jul 18, 2026
* 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>
tstapler and others added 13 commits July 18, 2026 22:30
…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
@tstapler tstapler reopened this Jul 19, 2026
tstapler and others added 2 commits July 19, 2026 10:00
* 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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.XnEEkqXtCI/backend
Wrote 14 feature files to /tmp/tmp.XnEEkqXtCI/backend
Wrote 40 feature files to /tmp/tmp.XnEEkqXtCI/backend
Wrote 6 feature files to /tmp/tmp.XnEEkqXtCI/backend
Wrote 10 feature files to /tmp/tmp.XnEEkqXtCI/backend

=== Backend Registry Diff ===
Committed: 174  Generated: 174  Divergence: 0.0%
⚠️  111 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 12/174 features have testIds (6.9%)

Divergence > 2% blocks merges. Coverage reporting is advisory only.

@github-actions

Copy link
Copy Markdown
Contributor

UX Analysis

Check Status Details
✅ Axe Core (WCAG 2.1 AA) success Critical/serious violations block merge
⚠️ Lighthouse Performance Score: unknown Warning if < 70 (non-blocking)
🤖 Claude UX Analysis Advisory See docs/qa/ for findings

Axe Core excludes terminal rendering areas (intentional design).
Lighthouse runs in desktop preset for this developer tool.

@github-actions

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:98: missing iteration count
benchmarks/go/tier1-baseline.txt:198: missing iteration count
tier1-bench.txt:98: missing iteration count
tier1-bench.txt:198: missing iteration count
goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/session
cpu: AMD EPYC 7763 64-Core Processor                
                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                            │              sec/op              │   sec/op     vs base              │
CircularBufferWrite_4KB-4                                          83.11n ± 1%   81.60n ± 3%  -1.82% (p=0.003 n=8)
CircularBufferWrite_4KB_Allocs-4                                   84.20n ± 1%   81.84n ± 2%  -2.80% (p=0.002 n=8)
CircularBufferGetRecent_4KB-4                                      482.0n ± 1%   512.4n ± 2%  +6.31% (p=0.001 n=7)
CircularBufferGetAll-4                                             3.702µ ± 3%   4.040µ ± 1%  +9.14% (p=0.000 n=8)
GetTimeSinceLastMeaningfulOutput_HotPath-4                         65.81n ± 0%   65.83n ± 0%       ~ (p=0.522 n=8)
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        33.06n ± 0%   33.08n ± 0%       ~ (p=0.928 n=7)
geomean                                                            173.4n        176.4n       +1.73%

                                            │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                            │               B/op               │     B/op      vs base                │
CircularBufferWrite_4KB-4                                         0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetRecent_4KB-4                                   4.000Ki ± 0%     4.000Ki ± 0%       ~ (p=1.000 n=7) ¹
CircularBufferGetAll-4                                          40.00Ki ± 0%     40.00Ki ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=7) ¹
geomean                                                                      ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                            │            allocs/op             │ allocs/op   vs base                │
CircularBufferWrite_4KB-4                                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetRecent_4KB-4                                     1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=7) ¹
CircularBufferGetAll-4                                            1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=7) ¹
geomean                                                                      ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/s                │     B/s       vs base              │
CircularBufferWrite_4KB-4                           45.90Gi ± 0%   46.75Gi ± 2%  +1.85% (p=0.003 n=8)
CircularBufferGetRecent_4KB-4                       7.914Gi ± 1%   7.445Gi ± 2%  -5.93% (p=0.001 n=7)
geomean                                             19.06Gi        18.66Gi       -2.12%

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                              │              sec/op              │   sec/op     vs base              │
StripANSI_PlainText-4                                6.865n ± 0%   6.878n ± 0%  +0.19% (p=0.015 n=8)
StripANSI_WithEscapes-4                              749.6n ± 0%   755.9n ± 1%       ~ (p=0.065 n=8)
ProcessOutput_InactiveState-4                        6.339n ± 0%   6.341n ± 0%       ~ (p=0.738 n=8)
geomean                                              31.95n        32.07n       +0.36%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             136.0 ± 0%     136.0 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             5.000 ± 0%     5.000 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/queue
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │              sec/op              │    sec/op     vs base              │
ReviewQueue_ConcurrentReads-4                        91.79n ± 3%   88.49n ± 11%  -3.60% (p=0.000 n=8)
ReviewQueue_Add-4                                    496.0n ± 0%   505.5n ±  0%  +1.93% (p=0.000 n=8)
geomean                                              213.4n        211.5n        -0.87%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   640.0 ± 0%     640.0 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/scrollback
                                      │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                      │              sec/op              │   sec/op     vs base               │
CircularBuffer_ConcurrentReadWrite-4                        3.951µ ±  3%   4.059µ ± 2%   +2.73% (p=0.048 n=8)
CircularBuffer_BurstAppend-4                                102.4µ ±  1%   102.3µ ± 1%        ~ (p=0.574 n=8)
CircularBuffer_GetLastN_LargeBuffer-4                       20.72µ ±  3%   20.73µ ± 2%        ~ (p=0.645 n=8)
CircularBuffer_GetRange_Sequential-4                        14.57µ ± 12%   11.44µ ± 2%  -21.47% (p=0.000 n=8)
CircularBufferAppend-4                                     102.75n ± 15%   98.70n ± 1%   -3.94% (p=0.000 n=8)
CircularBufferGetLastN-4                                    2.620µ ±  3%   2.289µ ± 1%  -12.65% (p=0.000 n=8)
CircularBufferConcurrentAppend-4                            127.8n ±  1%   127.1n ± 1%   -0.51% (p=0.008 n=8)
geomean                                                     3.293µ         3.112µ        -5.49%

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                      │               B/op               │     B/op      vs base                │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%   6.062Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%   62.50Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%   56.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%   28.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        24.00 ± 0%     24.00 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                    6.000Ki ± 0%   6.000Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              32.00 ± 0%     32.00 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                     3.077Ki        3.077Ki       +0.00%
¹ all samples are equal

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt           │
                                      │            allocs/op             │  allocs/op   vs base                │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                 1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                      1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       2.962         2.962       +0.00%
¹ all samples are equal

                             │ benchmarks/go/tier1-baseline.txt │        tier1-bench.txt        │
                             │               B/s                │     B/s       vs base         │
CircularBuffer_BurstAppend-4                       596.0Mi ± 1%   596.5Mi ± 1%  ~ (p=0.574 n=8)

pkg: github.com/tstapler/stapler-squad/session/tmux
                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                             │              sec/op              │   sec/op     vs base              │
StripANSICodes_PlainText-4                          6.880n ± 0%   6.880n ± 0%       ~ (p=0.665 n=8)
StripANSICodes_WithEscapes-4                        692.4n ± 0%   691.7n ± 1%       ~ (p=0.525 n=8)
IsBanner_PlainText-4                                480.6n ± 0%   477.7n ± 0%  -0.60% (p=0.002 n=8)
geomean                                             131.8n        131.5n       -0.23%

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │               B/op               │    B/op     vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       56.00 ± 0%     56.00 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │            allocs/op             │ allocs/op   vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/tokens
                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                   │              sec/op              │   sec/op     vs base              │
TokenParser_ProcessUserEntry-4                            5.262m ± 1%   5.420m ± 1%  +3.00% (p=0.000 n=8)
DetectCommandsInText/NoSlash-4                            7.490n ± 0%   7.503n ± 0%  +0.17% (p=0.006 n=8)
DetectCommandsInText/WithCommand-4                        1.668µ ± 0%   1.704µ ± 2%  +2.19% (p=0.047 n=8)
geomean                                                   4.036µ        4.107µ       +1.78%

                                   │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                   │               B/op               │     B/op      vs base                │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%     11.02Mi ± 0%  +0.00% (p=0.000 n=8)
DetectCommandsInText/NoSlash-4                           0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       433.0 ± 0%       433.0 ± 0%       ~ (p=1.000 n=8)
geomean                                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                   │            allocs/op             │ allocs/op   vs base                │
TokenParser_ProcessUserEntry-4                           34.00 ± 0%     34.00 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/NoSlash-4                           0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       6.000 ± 0%     6.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/unfinished
                               │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                               │              sec/op              │   sec/op     vs base              │
DiffShortstat/GitVCSReader-4                          3.112m ± 0%   3.280m ± 2%  +5.40% (p=0.000 n=8)
DiffShortstat/GoGitVCSReader-4                        76.76n ± 0%   78.47n ± 2%  +2.23% (p=0.000 n=8)
DiffShortstatCached-4                                 75.58n ± 0%   75.47n ± 0%       ~ (p=0.457 n=8)
geomean                                               2.623µ        2.688µ       +2.47%

                               │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                               │               B/op               │     B/op      vs base                │
DiffShortstat/GitVCSReader-4                       56.56Ki ± 0%     56.57Ki ± 0%       ~ (p=0.721 n=8)
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
DiffShortstatCached-4                                0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                         ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                               │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                               │            allocs/op             │ allocs/op   vs base                │
DiffShortstat/GitVCSReader-4                         360.0 ± 0%     360.0 ± 0%       ~ (p=1.000 n=8)
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
DiffShortstatCached-4                                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                         ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@github-actions

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 5ms (▲ slower +35.5%; baseline: 4ms)
list-sessions-total-mean: 10ms (▲ slower +62.4%; baseline: 6ms)

@github-actions

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 17 KB/s ▲ +6.2% (baseline: 16 KB/s)
terminal-throughput-p50: 17 KB/s ▲ +8.3% (baseline: 16 KB/s)

github-actions Bot and others added 7 commits July 19, 2026 17:18
… 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
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.

2 participants