Skip to content

fix(backlog): auto-respawn triage for items orphaned in idea#169

Open
tstapler wants to merge 13 commits into
mainfrom
worktree-agent-aa4517075c8f84c7f
Open

fix(backlog): auto-respawn triage for items orphaned in idea#169
tstapler wants to merge 13 commits into
mainfrom
worktree-agent-aa4517075c8f84c7f

Conversation

@tstapler

Copy link
Copy Markdown
Owner

Summary

  • reconcileOrphanedTriageItems previously only detected and notified when an idea-status item's triage session died mid-run — its own doc comment falsely claimed the item "self-heals once it leaves idea," but nothing ever moved it out of idea.
  • Adds a TriageRespawner seam (mirrors PRFixSpawner) and BacklogService.AutoRetriggerTriage, which re-triggers triage via the existing TriggerTriage entry point once the item is confirmed orphaned (2h+ stale, per the existing staleness gate), with an active-session re-check and a maxAutoReworkIterations cap to prevent runaway retriggers.
  • Corrects the misleading doc comment on reconcileOrphanedTriageItems to describe what the code now actually does.

Context

Found during a systematic autonomy-gap audit (docs/tasks/backlog-feature-improvement.md, row 8): idea-status items whose triage session crashed, was killed, or never completed after a server restart were flagged and notified but never actually retried — they sat stuck at idea forever until a human noticed and manually re-triggered triage.

Changes

  • session/backlog_lifecycle.go: new TriageRespawner interface + SetTriageRespawner/getTriageRespawner; reconcileOrphanedTriageItems now dispatches an async respawn (bounded by the existing reviewSem limiter) once past the existing 2h staleness threshold, in addition to the existing notify; doc comment corrected.
  • server/services/backlog_service_triage.go: new AutoRetriggerTriage (implements TriageRespawner) and hasFreshTriageSession helper — status guard, "might still be running" re-check (headless triage has no liveness signal, so elapsed time since session creation is the proxy), rework-cap guard reusing maxAutoReworkIterations/notifyReworkCapHit, then delegates to the existing TriggerTriage RPC handler.
  • server/dependencies.go: wires backlogLifecycleListener.SetTriageRespawner(backlogSvc).
  • Tests: TestReconcileOrphanedTriageItems_AutoRetriggersTriage_When_SessionStale, TestReconcileOrphanedTriageItems_NoRespawn_WhenNoTriageRespawnerConfigured (session package); TestAutoRetriggerTriage_ReworkCapHit_LeavesInIdeaAndNotifies, TestAutoRetriggerTriage_FreshTriageSession_SkipsWithoutDoubleSpawn, TestAutoRetriggerTriage_NoActiveSession_TriggersTriage (server/services package).

Impact

  • Scope: backlog reconciliation loop only (idea-status items with a dead triage session). No API/proto changes.
  • Breaking Changes: none.
  • Performance: negligible — respawn dispatch is async and bounded by the existing semaphore; the actual headless triage call is bounded by its own existing triageSem (max 8 concurrent).
  • Dependencies: none new.

Testing

  • go test ./session/... — full suite passes
  • go test ./server/services/... — full suite passes
  • golangci-lint run --enable=nilnil,staticcheck,ineffassign,govet ./session/... ./server/... — 0 issues
  • Manual verification: not performed (reconciler runs on a 60s tick in production; behavior verified via unit tests simulating the stale-session/cap/fresh-session scenarios)

Reviewer Notes

  • Focus areas: the hasFreshTriageSession staleness re-check (no liveness signal exists for headless triage sessions, unlike work/review sessions) and the choice to reuse l.reviewSem for the dispatch limiter (matches the precedent set by the sibling abandoned-review-respawn fix, PR fix(backlog): auto-respawn review for items stuck abandoned in review #168, currently open/unmerged).
  • Known limitations: no separate post-detection grace period before respawn — the existing 2h staleness threshold itself is the gate, matching reconcileStaleWorkSessions' established pattern in this file.
  • Follow-up tasks: none identified.

tstapler and others added 6 commits July 17, 2026 16:52
reconcileOrphanedTriageItems (session/backlog_lifecycle.go) previously only
wrote a stuck row and notified an operator once a triage session's crash/kill/
restart went undetected past the 2h staleness threshold — its own doc comment
claimed the item "self-heals once it leaves idea," but nothing ever moved it
out of idea, so it sat there forever until a human noticed and manually
re-triggered triage (docs/tasks/backlog-feature-improvement.md, row 8).

Adds a TriageRespawner seam (mirrors the existing PRFixSpawner pattern) and
wires BacklogService.AutoRetriggerTriage into it via the existing TriggerTriage
entry point, following the same shape as AutoReopenForPRFix/
AutoReopenAfterFailedReview: status guard, an elapsed-time "might still be
running" check (headless triage has no liveness signal, so age is the only
proxy — mirrors reconcileOrphanedTriageItems' own staleness gate), and a cap
on triage-session count (reusing maxAutoReworkIterations/notifyReworkCapHit)
to stop a persistently-crashing item from re-triggering forever. Dispatched
async under the existing reviewSem limiter so TriggerTriage's dispatch never
blocks the synchronous reconcile sweep. Fires on the same tick the 2h
staleness threshold is crossed — no separate grace period, matching
reconcileStaleWorkSessions' established pattern (the staleness check itself is
the "don't act on first detection" gate).

Doc comment on reconcileOrphanedTriageItems corrected to describe what the
code now actually does, rather than the previously-false self-heal claim.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…us-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.
…icy 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.
…ng 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.
…075c8f84c7f

# Conflicts:
#	server/dependencies.go
#	server/services/backlog_service_triage.go
#	session/backlog_lifecycle.go
#	session/backlog_lifecycle_test.go
…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.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.7Qc91Ys1OD/backend
Wrote 14 feature files to /tmp/tmp.7Qc91Ys1OD/backend
Wrote 38 feature files to /tmp/tmp.7Qc91Ys1OD/backend
Wrote 6 feature files to /tmp/tmp.7Qc91Ys1OD/backend
Wrote 10 feature files to /tmp/tmp.7Qc91Ys1OD/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 10/172 features have testIds (5.8%)

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

@github-actions

github-actions Bot commented Jul 18, 2026

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:199: 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 │
                                            │              sec/op              │
CircularBufferWrite_4KB-4                                          80.34n ± 2%
CircularBufferWrite_4KB_Allocs-4                                   81.50n ± 1%
CircularBufferGetRecent_4KB-4                                      489.8n ± 1%
CircularBufferGetAll-4                                             3.909µ ± 8%
GetTimeSinceLastMeaningfulOutput_HotPath-4                         65.54n ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        33.09n ± 0%
geomean                                                            173.4n

                                            │ benchmarks/go/tier1-baseline.txt │
                                            │               B/op               │
CircularBufferWrite_4KB-4                                         0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%
CircularBufferGetRecent_4KB-4                                   4.000Ki ± 0%
CircularBufferGetAll-4                                          40.00Ki ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%
geomean                                                                      ¹
¹ summaries must be >0 to compute geomean

                                            │ benchmarks/go/tier1-baseline.txt │
                                            │            allocs/op             │
CircularBufferWrite_4KB-4                                         0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%
CircularBufferGetRecent_4KB-4                                     1.000 ± 0%
CircularBufferGetAll-4                                            1.000 ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%
geomean                                                                      ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/s                │
CircularBufferWrite_4KB-4                           47.48Gi ± 2%
CircularBufferGetRecent_4KB-4                       7.788Gi ± 1%
geomean                                             19.23Gi

cpu: AMD EPYC 9V74 80-Core Processor                
                                            │ tier1-bench.txt │
                                            │     sec/op      │
CircularBufferWrite_4KB-4                         62.11n ± 0%
CircularBufferWrite_4KB_Allocs-4                  62.22n ± 2%
CircularBufferGetRecent_4KB-4                     440.4n ± 5%
CircularBufferGetAll-4                            3.128µ ± 3%
GetTimeSinceLastMeaningfulOutput_HotPath-4        54.44n ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4       26.78n ± 0%
geomean                                           140.7n

                                            │ tier1-bench.txt │
                                            │      B/op       │
CircularBufferWrite_4KB-4                        0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                 0.000 ± 0%
CircularBufferGetRecent_4KB-4                  4.000Ki ± 0%
CircularBufferGetAll-4                         40.00Ki ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4       0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      0.000 ± 0%
geomean                                                     ¹
¹ summaries must be >0 to compute geomean

                                            │ tier1-bench.txt │
                                            │    allocs/op    │
CircularBufferWrite_4KB-4                        0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                 0.000 ± 0%
CircularBufferGetRecent_4KB-4                    1.000 ± 0%
CircularBufferGetAll-4                           1.000 ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4       0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      0.000 ± 0%
geomean                                                     ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │       B/s       │
CircularBufferWrite_4KB-4          61.42Gi ± 2%
CircularBufferGetRecent_4KB-4      8.663Gi ± 5%
geomean                            23.07Gi

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
cpu: AMD EPYC 7763 64-Core Processor                
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
StripANSI_PlainText-4                                6.873n ± 0%
StripANSI_WithEscapes-4                              746.7n ± 0%
ProcessOutput_InactiveState-4                        6.314n ± 1%
geomean                                              31.88n

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/op               │
StripANSI_PlainText-4                               0.000 ± 0%
StripANSI_WithEscapes-4                             136.0 ± 0%
ProcessOutput_InactiveState-4                       0.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │            allocs/op             │
StripANSI_PlainText-4                               0.000 ± 0%
StripANSI_WithEscapes-4                             5.000 ± 0%
ProcessOutput_InactiveState-4                       0.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
StripANSI_PlainText-4               5.461n ± 2%
StripANSI_WithEscapes-4             509.5n ± 3%
ProcessOutput_InactiveState-4       5.188n ± 0%
geomean                             24.35n

                              │ tier1-bench.txt │
                              │      B/op       │
StripANSI_PlainText-4              0.000 ± 0%
StripANSI_WithEscapes-4            136.0 ± 0%
ProcessOutput_InactiveState-4      0.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │    allocs/op    │
StripANSI_PlainText-4              0.000 ± 0%
StripANSI_WithEscapes-4            5.000 ± 0%
ProcessOutput_InactiveState-4      0.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/queue
cpu: AMD EPYC 7763 64-Core Processor                
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
ReviewQueue_ConcurrentReads-4                        83.19n ± 6%
ReviewQueue_Add-4                                    506.0n ± 1%
geomean                                              205.2n

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/op               │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%
ReviewQueue_Add-4                                   640.0 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │            allocs/op             │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%
ReviewQueue_Add-4                                   4.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
ReviewQueue_ConcurrentReads-4      69.02n ± 11%
ReviewQueue_Add-4                  400.9n ±  2%
geomean                            166.3n

                              │ tier1-bench.txt │
                              │      B/op       │
ReviewQueue_ConcurrentReads-4      0.000 ± 0%
ReviewQueue_Add-4                  640.0 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │    allocs/op    │
ReviewQueue_ConcurrentReads-4      0.000 ± 0%
ReviewQueue_Add-4                  4.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/scrollback
cpu: AMD EPYC 7763 64-Core Processor                
                                      │ benchmarks/go/tier1-baseline.txt │
                                      │              sec/op              │
CircularBuffer_ConcurrentReadWrite-4                         3.974µ ± 2%
CircularBuffer_BurstAppend-4                                 102.4µ ± 1%
CircularBuffer_GetLastN_LargeBuffer-4                        19.93µ ± 0%
CircularBuffer_GetRange_Sequential-4                         10.92µ ± 1%
CircularBufferAppend-4                                       97.99n ± 0%
CircularBufferGetLastN-4                                     2.260µ ± 2%
CircularBufferConcurrentAppend-4                             127.1n ± 1%
geomean                                                      3.057µ

                                      │ benchmarks/go/tier1-baseline.txt │
                                      │               B/op               │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%
CircularBufferAppend-4                                        24.00 ± 0%
CircularBufferGetLastN-4                                    6.000Ki ± 0%
CircularBufferConcurrentAppend-4                              32.00 ± 0%
geomean                                                     3.077Ki

                                      │ benchmarks/go/tier1-baseline.txt │
                                      │            allocs/op             │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%
CircularBuffer_BurstAppend-4                                 1.000k ± 0%
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%
CircularBufferAppend-4                                        1.000 ± 0%
CircularBufferGetLastN-4                                      1.000 ± 0%
CircularBufferConcurrentAppend-4                              1.000 ± 0%
geomean                                                       2.962

                             │ benchmarks/go/tier1-baseline.txt │
                             │               B/s                │
CircularBuffer_BurstAppend-4                       596.2Mi ± 1%

cpu: AMD EPYC 9V74 80-Core Processor                
                                      │ tier1-bench.txt │
                                      │     sec/op      │
CircularBuffer_ConcurrentReadWrite-4        2.761µ ± 1%
CircularBuffer_BurstAppend-4                83.69µ ± 3%
CircularBuffer_GetLastN_LargeBuffer-4       15.10µ ± 2%
CircularBuffer_GetRange_Sequential-4        8.134µ ± 1%
CircularBufferAppend-4                      79.73n ± 1%
CircularBufferGetLastN-4                    1.806µ ± 1%
CircularBufferConcurrentAppend-4            103.2n ± 0%
geomean                                     2.372µ

                                      │ tier1-bench.txt │
                                      │      B/op       │
CircularBuffer_ConcurrentReadWrite-4       6.062Ki ± 0%
CircularBuffer_BurstAppend-4               62.50Ki ± 0%
CircularBuffer_GetLastN_LargeBuffer-4      56.00Ki ± 0%
CircularBuffer_GetRange_Sequential-4       28.00Ki ± 0%
CircularBufferAppend-4                       24.00 ± 0%
CircularBufferGetLastN-4                   6.000Ki ± 0%
CircularBufferConcurrentAppend-4             32.00 ± 0%
geomean                                    3.077Ki

                                      │ tier1-bench.txt │
                                      │    allocs/op    │
CircularBuffer_ConcurrentReadWrite-4         2.000 ± 0%
CircularBuffer_BurstAppend-4                1.000k ± 0%
CircularBuffer_GetLastN_LargeBuffer-4        1.000 ± 0%
CircularBuffer_GetRange_Sequential-4         1.000 ± 0%
CircularBufferAppend-4                       1.000 ± 0%
CircularBufferGetLastN-4                     1.000 ± 0%
CircularBufferConcurrentAppend-4             1.000 ± 0%
geomean                                      2.962

                             │ tier1-bench.txt │
                             │       B/s       │
CircularBuffer_BurstAppend-4      729.3Mi ± 3%

pkg: github.com/tstapler/stapler-squad/session/tmux
cpu: AMD EPYC 7763 64-Core Processor                
                             │ benchmarks/go/tier1-baseline.txt │
                             │              sec/op              │
StripANSICodes_PlainText-4                          6.877n ± 5%
StripANSICodes_WithEscapes-4                        694.1n ± 0%
IsBanner_PlainText-4                                484.9n ± 0%
geomean                                             132.3n

                             │ benchmarks/go/tier1-baseline.txt │
                             │               B/op               │
StripANSICodes_PlainText-4                         0.000 ± 0%
StripANSICodes_WithEscapes-4                       56.00 ± 0%
IsBanner_PlainText-4                               0.000 ± 0%
geomean                                                       ¹
¹ summaries must be >0 to compute geomean

                             │ benchmarks/go/tier1-baseline.txt │
                             │            allocs/op             │
StripANSICodes_PlainText-4                         0.000 ± 0%
StripANSICodes_WithEscapes-4                       4.000 ± 0%
IsBanner_PlainText-4                               0.000 ± 0%
geomean                                                       ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                             │ tier1-bench.txt │
                             │     sec/op      │
StripANSICodes_PlainText-4         5.745n ± 0%
StripANSICodes_WithEscapes-4       473.8n ± 0%
IsBanner_PlainText-4               359.7n ± 1%
geomean                            99.29n

                             │ tier1-bench.txt │
                             │      B/op       │
StripANSICodes_PlainText-4        0.000 ± 0%
StripANSICodes_WithEscapes-4      56.00 ± 0%
IsBanner_PlainText-4              0.000 ± 0%
geomean                                      ¹
¹ summaries must be >0 to compute geomean

                             │ tier1-bench.txt │
                             │    allocs/op    │
StripANSICodes_PlainText-4        0.000 ± 0%
StripANSICodes_WithEscapes-4      4.000 ± 0%
IsBanner_PlainText-4              0.000 ± 0%
geomean                                      ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/tokens
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ benchmarks/go/tier1-baseline.txt │
                                   │              sec/op              │
TokenParser_ProcessUserEntry-4                            5.352m ± 1%
DetectCommandsInText/NoSlash-4                            7.497n ± 0%
DetectCommandsInText/WithCommand-4                        1.659µ ± 1%
geomean                                                   4.053µ

                                   │ benchmarks/go/tier1-baseline.txt │
                                   │               B/op               │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%
DetectCommandsInText/NoSlash-4                           0.000 ± 0%
DetectCommandsInText/WithCommand-4                       433.0 ± 0%
geomean                                                             ¹
¹ summaries must be >0 to compute geomean

                                   │ benchmarks/go/tier1-baseline.txt │
                                   │            allocs/op             │
TokenParser_ProcessUserEntry-4                           34.00 ± 0%
DetectCommandsInText/NoSlash-4                           0.000 ± 0%
DetectCommandsInText/WithCommand-4                       6.000 ± 0%
geomean                                                             ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                                   │ tier1-bench.txt │
                                   │     sec/op      │
TokenParser_ProcessUserEntry-4           4.280m ± 1%
DetectCommandsInText/NoSlash-4           5.184n ± 5%
DetectCommandsInText/WithCommand-4       1.155µ ± 1%
geomean                                  2.948µ

                                   │ tier1-bench.txt │
                                   │      B/op       │
TokenParser_ProcessUserEntry-4        11.02Mi ± 0%
DetectCommandsInText/NoSlash-4          0.000 ± 0%
DetectCommandsInText/WithCommand-4      433.0 ± 0%
geomean                                            ¹
¹ summaries must be >0 to compute geomean

                                   │ tier1-bench.txt │
                                   │    allocs/op    │
TokenParser_ProcessUserEntry-4          34.00 ± 0%
DetectCommandsInText/NoSlash-4          0.000 ± 0%
DetectCommandsInText/WithCommand-4      6.000 ± 0%
geomean                                            ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/unfinished
cpu: AMD EPYC 7763 64-Core Processor                
                               │ benchmarks/go/tier1-baseline.txt │
                               │              sec/op              │
DiffShortstat/GitVCSReader-4                          3.176m ± 1%
DiffShortstat/GoGitVCSReader-4                        76.92n ± 0%
DiffShortstatCached-4                                 75.55n ± 1%
geomean                                               2.643µ

                               │ benchmarks/go/tier1-baseline.txt │
                               │               B/op               │
DiffShortstat/GitVCSReader-4                       56.57Ki ± 0%
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%
DiffShortstatCached-4                                0.000 ± 0%
geomean                                                         ¹
¹ summaries must be >0 to compute geomean

                               │ benchmarks/go/tier1-baseline.txt │
                               │            allocs/op             │
DiffShortstat/GitVCSReader-4                         360.0 ± 0%
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%
DiffShortstatCached-4                                0.000 ± 0%
geomean                                                         ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                               │ tier1-bench.txt │
                               │     sec/op      │
DiffShortstat/GitVCSReader-4         2.681m ± 1%
DiffShortstat/GoGitVCSReader-4       62.70n ± 0%
DiffShortstatCached-4                62.93n ± 0%
geomean                              2.195µ

                               │ tier1-bench.txt │
                               │      B/op       │
DiffShortstat/GitVCSReader-4      56.58Ki ± 0%
DiffShortstat/GoGitVCSReader-4      0.000 ± 0%
DiffShortstatCached-4               0.000 ± 0%
geomean                                        ¹
¹ summaries must be >0 to compute geomean

                               │ tier1-bench.txt │
                               │    allocs/op    │
DiffShortstat/GitVCSReader-4        360.0 ± 0%
DiffShortstat/GoGitVCSReader-4      0.000 ± 0%
DiffShortstatCached-4               0.000 ± 0%
geomean                                        ¹
¹ summaries must be >0 to compute geomean

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 5ms (▼ faster -34.1%; baseline: 7ms)
list-sessions-total-mean: 6ms (▼ faster -54.1%; baseline: 13ms)

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 15 KB/s ▼ -0.7% (baseline: 16 KB/s)
terminal-throughput-p50: 16 KB/s ▲ +1.9% (baseline: 16 KB/s)

@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 18, 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 and others added 6 commits July 17, 2026 17:58
…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
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
…ext 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
…re 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

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

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 10/172 features have testIds (5.8%)

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

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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

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

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 10/172 features have testIds (5.8%)

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

tstapler added a commit that referenced this pull request Jul 20, 2026
…kstop (#195)

.backlog-context.md and .claude/commands/backlog/*.md are written into every
backlog work-session worktree, then git-excluded via $GIT_DIR/info/exclude.
That only stops NEW files from being staged — it does nothing once a file is
already tracked, which is why this repo's own history has ~20 manual
"chore(backlog): untrack backlog context/command files" commits, and why two
currently-open PRs (#169, #170) both carry an incidental deletion of a stale,
committed .backlog-context.md from an unrelated item's context.

WriteSlashCommands and WriteBacklogContextFile now call
selfHealWorktreeScaffolding before writing: it walks the git index (via
go-git, not a subshell) for any entry matching backlogExcludePatterns and
untracks it (git-rm-cached semantics — working tree file is left alone), so
a branch that ever got one of these files committed self-heals on the very
next spawn/reattach/reopen instead of requiring a manual untrack commit.

Also adds a CI backstop (.github/workflows/backlog-scaffolding-guard.yml)
that fails any PR whose diff adds/modifies a backlogExcludePatterns file, as
a second, independent layer for this repo. This only protects stapler-squad's
own repo — it cannot reach into the arbitrary target repos backlog items
point at, which is why the self-heal above (not CI) is the primary fix.

On the "scoped by session/backlog item so it's not reused poorly" half of
the request: chose to harden the existing worktree-relative storage rather
than relocate it to a session/item-scoped path outside the worktree
(~/.stapler-squad/backlog-context/<item>/...). Both WriteBacklogContextFile
and WriteSlashCommands already do a full atomic overwrite on every
spawn/reattach/reopen, so no session ever reads content from a different
item/session — the actual "reused poorly" risk was exclusively the
already-tracked git pollution this fix closes. Relocating storage is a
larger, real architecture change (touches taskProtocolBlock's literal
`.backlog-context.md` references, needs a session UUID that doesn't exist
yet at the pre-spawn write callsite, and increases collision surface with
other agents' concurrent work in these same files) and is left as a
follow-up rather than folded into this bug fix.

CleanupSlashCommands/CleanupBacklogContextFile remain uncalled from any
production teardown path, and that's intentional, not an oversight:
shipViaAgentOrFallback relies on ship.md still existing after a work session
exits review to re-invoke /backlog/ship as a one-shot call. Wiring cleanup
into a review-exit teardown would delete ship.md out from under that path.
Documented this directly in both functions' doc comments so it isn't
"fixed" again by mistake.

Regression tests: a worktree with .backlog-context.md (or
.claude/commands/backlog/status.md) already committed gets it untracked on
the next Write call with fresh on-disk content; respawning the same
worktree path for a different item never leaks the prior item's content;
the self-heal helper no-ops cleanly on a non-git directory and leaves
unrelated tracked files alone.


Claude-Session: https://claude.ai/code/session_01BxNAMeGteuzNyN46Q4zAn1

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant