Skip to content

fix(backlog): escalate bouncing items to the rework-cap terminal state#170

Open
tstapler wants to merge 4 commits into
mainfrom
worktree-agent-ab0fc502ce6a6c03b
Open

fix(backlog): escalate bouncing items to the rework-cap terminal state#170
tstapler wants to merge 4 commits into
mainfrom
worktree-agent-ab0fc502ce6a6c03b

Conversation

@tstapler

Copy link
Copy Markdown
Owner

Summary

  • reconcileBouncingItems detected non-converging bouncing (item thrashes in_progress<->review with no PASS verdict) but only sent a notification — nothing stopped the auto-reopen loop, so a bouncing item just kept bouncing forever with a human expected to notice.
  • AutoReopenAfterFailedReview (the actual respawn choke point, invoked synchronously the moment any non-PASS review verdict lands) now applies the identical isBouncing test the periodic detector uses, and — when it trips — escalates through the existing notifyReworkCapHit terminal treatment (same StuckReasonReworkCap row, same notification shape) instead of respawning again.
  • This closes a real divergence: workCount (the existing rework-cap counter) only counts actually-created work sessions, but the spawn-failure rollback path records an extra in_progress->review status event with no new work session, so the cycle count can outpace the work-session count and evade the workCount cap entirely.
  • reconcileBouncingItems now checks for an already-open rework_cap row before notifying, so the two detectors don't fire two differently-shaped "needs manual attention" signals for the same underlying non-convergence.

Context

Scoped from the 2026-07-17 evening "systematic autonomy-gap audit" in docs/tasks/backlog-feature-improvement.md, which flagged bouncing as a real gap: "no escalation/different-approach retry once non-converging, just a notification." Live example in that audit: an item bounced in_progress<->review 4 times across 3 sessions with no PASS verdict, dead with no self-service retry affordance.

Changes

  • session/stuck_decisions.go: exported BounceThreshold, BounceLookback, IsBouncing (thin aliases/wrappers around the existing unexported values) so server/services can reuse the identical bounce test.
  • session/storage.go: added Storage.CountReviewCyclesSince wrapper (mirrors the existing GetMostRecentReviewVerdictForItem wrapper pattern) so the services package can query cycle counts without reaching into the unexported EntRepository.
  • server/services/backlog_service_triage.go: AutoReopenAfterFailedReview now runs the bounce check after the existing workCount >= maxAutoReworkIterations check and, if it trips, calls notifyReworkCapHit and returns without respawning.
  • session/backlog_lifecycle.go: reconcileBouncingItems now skips its own notification (but still writes/marks the bouncing row for UI visibility) when an open rework_cap row already exists for the item.
  • Tests: TestAutoReopenAfterFailedReview_should_escalateToReworkCap_When_BouncingBelowWorkCountCap, TestAutoReopenAfterFailedReview_should_reopenNormally_When_BelowBounceThreshold, TestReconcileBouncingItems_should_skipDuplicateNotify_When_AlreadyEscalatedToReworkCap.

Impact

  • Scope: session and server/services packages only (backlog reconciliation loop). No proto/schema/UI changes.
  • Breaking Changes: none — purely additive terminal-state behavior for a condition that previously looped forever.
  • Performance: negligible — one additional indexed query (CountReviewCyclesSince) per non-PASS review verdict, same query reconcileBouncingItems already runs periodically.
  • Dependencies: none.

Testing

  • go build ./session/... ./server/services/... — clean
  • go vet ./session/... ./server/services/... — clean
  • golangci-lint run ./session/... ./server/services/... — 0 issues
  • go test ./session/... — full suite green
  • go test ./server/services/... — full suite green
  • New regression tests pass individually and as part of the full suite

Reviewer Notes

  • Focus areas: the coordination logic between reconcileBouncingItems (periodic) and AutoReopenAfterFailedReview (synchronous) — the periodic sweep is the backstop for reopen paths that bypass the synchronous check (e.g. a manual reopen).
  • Known limitations: if the periodic sweep's tick races ahead of the synchronous escalation (unlikely given the synchronous path fires immediately on verdict), the operator could still see the bouncing notification followed shortly by the rework-cap notification — a pre-existing race shape, not a regression, and not the common case.
  • Follow-up tasks: none identified; this is a bounded fix per the audit's "bouncing (thrashes indefinitely)" priority-2 item.

🤖 Generated with Claude Code

reconcileBouncingItems detected items non-convergently bouncing between
in_progress and review (>= bounceThreshold cycles, no PASS verdict) but only
sent a notification -- it has no way to stop the auto-reopen loop, so a
bouncing item just kept bouncing forever with a human expected to notice.

The existing rework_cap detector (AutoReopenAfterFailedReview's workCount
check) can miss the same non-convergence: workCount only counts actually
-created work sessions, but the spawn-failure rollback path records an
extra in_progress->review status event without a new work session, so the
cycle count can outpace the work-session count.

Apply the same isBouncing test AutoReopenAfterFailedReview's respawn choke
point uses and, when it trips, escalate through the existing
notifyReworkCapHit terminal treatment -- the same StuckReasonReworkCap row
and notification shape rework_cap already uses -- rather than respawning
again. reconcileBouncingItems now also checks for an already-open rework_cap
row before notifying, so the two detectors don't fire two differently
-shaped "needs manual attention" signals for the same underlying condition.

Regression tests: TestAutoReopenAfterFailedReview_should_escalateToReworkCap_When_BouncingBelowWorkCountCap,
TestAutoReopenAfterFailedReview_should_reopenNormally_When_BelowBounceThreshold,
TestReconcileBouncingItems_should_skipDuplicateNotify_When_AlreadyEscalatedToReworkCap

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.xEAGI5GLPQ/backend
Wrote 14 feature files to /tmp/tmp.xEAGI5GLPQ/backend
Wrote 38 feature files to /tmp/tmp.xEAGI5GLPQ/backend
Wrote 6 feature files to /tmp/tmp.xEAGI5GLPQ/backend
Wrote 10 feature files to /tmp/tmp.xEAGI5GLPQ/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: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                                          80.34n ± 2%   83.35n ± 2%  +3.74% (p=0.000 n=8)
CircularBufferWrite_4KB_Allocs-4                                   81.50n ± 1%   84.51n ± 2%  +3.69% (p=0.001 n=8)
CircularBufferGetRecent_4KB-4                                      489.8n ± 1%   469.6n ± 2%  -4.12% (p=0.001 n=7)
CircularBufferGetAll-4                                             3.909µ ± 8%   3.721µ ± 3%  -4.81% (p=0.003 n=8)
GetTimeSinceLastMeaningfulOutput_HotPath-4                         65.54n ± 0%   65.84n ± 0%  +0.45% (p=0.003 n=8)
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        33.09n ± 0%   33.07n ± 0%       ~ (p=0.089 n=7)
geomean                                                            173.4n        173.0n       -0.24%

                                            │ 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                           47.48Gi ± 2%   45.77Gi ± 0%  -3.60% (p=0.000 n=8)
CircularBufferGetRecent_4KB-4                       7.788Gi ± 1%   8.124Gi ± 2%  +4.32% (p=0.001 n=7)
geomean                                             19.23Gi        19.28Gi       +0.28%

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.873n ± 0%   6.865n ± 0%  -0.11% (p=0.014 n=8)
StripANSI_WithEscapes-4                              746.7n ± 0%   743.0n ± 0%  -0.49% (p=0.002 n=8)
ProcessOutput_InactiveState-4                        6.314n ± 1%   6.292n ± 1%       ~ (p=0.137 n=8)
geomean                                              31.88n        31.78n       -0.32%

                              │ 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                        83.19n ± 6%   87.52n ± 6%  +5.20% (p=0.007 n=8)
ReviewQueue_Add-4                                    506.0n ± 1%   498.6n ± 1%  -1.46% (p=0.000 n=8)
geomean                                              205.2n        208.9n       +1.82%

                              │ 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.974µ ± 2%   3.764µ ± 3%  -5.29% (p=0.002 n=8)
CircularBuffer_BurstAppend-4                                 102.4µ ± 1%   101.3µ ± 1%  -1.00% (p=0.000 n=8)
CircularBuffer_GetLastN_LargeBuffer-4                        19.93µ ± 0%   19.93µ ± 3%       ~ (p=0.938 n=8)
CircularBuffer_GetRange_Sequential-4                         10.92µ ± 1%   10.79µ ± 1%  -1.19% (p=0.003 n=8)
CircularBufferAppend-4                                       97.99n ± 0%   97.61n ± 0%  -0.39% (p=0.026 n=8)
CircularBufferGetLastN-4                                     2.260µ ± 2%   2.222µ ± 1%  -1.70% (p=0.011 n=8)
CircularBufferConcurrentAppend-4                             127.1n ± 1%   123.2n ± 1%  -3.03% (p=0.000 n=8)
geomean                                                      3.057µ        3.001µ       -1.82%

                                      │ 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.2Mi ± 1%   602.2Mi ± 0%  +1.01% (p=0.000 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.877n ± 5%   6.869n ± 1%       ~ (p=0.195 n=8)
StripANSICodes_WithEscapes-4                        694.1n ± 0%   692.1n ± 0%       ~ (p=0.111 n=8)
IsBanner_PlainText-4                                484.9n ± 0%   483.5n ± 0%  -0.30% (p=0.011 n=8)
geomean                                             132.3n        132.0n       -0.24%

                             │ 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.352m ± 1%   5.210m ± 2%  -2.66% (p=0.007 n=8)
DetectCommandsInText/NoSlash-4                            7.497n ± 0%   7.498n ± 0%       ~ (p=0.903 n=8)
DetectCommandsInText/WithCommand-4                        1.659µ ± 1%   1.653µ ± 1%       ~ (p=0.291 n=8)
geomean                                                   4.053µ        4.012µ       -1.01%

                                   │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                   │               B/op               │     B/op      vs base                │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%     11.02Mi ± 0%       ~ (p=0.285 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.176m ± 1%   3.128m ± 1%  -1.50% (p=0.000 n=8)
DiffShortstat/GoGitVCSReader-4                        76.92n ± 0%   76.68n ± 0%  -0.32% (p=0.046 n=8)
DiffShortstatCached-4                                 75.55n ± 1%   75.55n ± 0%       ~ (p=0.878 n=8)
geomean                                               2.643µ        2.627µ       -0.61%

                               │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                               │               B/op               │     B/op      vs base                │
DiffShortstat/GitVCSReader-4                       56.57Ki ± 0%     56.57Ki ± 0%       ~ (p=0.505 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

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 6ms (▼ faster -13.8%; baseline: 7ms)
list-sessions-total-mean: 7ms (▼ faster -44.1%; baseline: 13ms)

@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.

@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

Frontend Terminal Throughput

terminal-throughput-mean: 16 KB/s ▲ +2.6% (baseline: 16 KB/s)
terminal-throughput-p50: 16 KB/s ▲ +3.8% (baseline: 16 KB/s)

@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.wv1f3iPLVK/backend
Wrote 14 feature files to /tmp/tmp.wv1f3iPLVK/backend
Wrote 38 feature files to /tmp/tmp.wv1f3iPLVK/backend
Wrote 6 feature files to /tmp/tmp.wv1f3iPLVK/backend
Wrote 10 feature files to /tmp/tmp.wv1f3iPLVK/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.HyrxnX8seQ/backend
Wrote 14 feature files to /tmp/tmp.HyrxnX8seQ/backend
Wrote 38 feature files to /tmp/tmp.HyrxnX8seQ/backend
Wrote 6 feature files to /tmp/tmp.HyrxnX8seQ/backend
Wrote 10 feature files to /tmp/tmp.HyrxnX8seQ/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>
@tstapler
tstapler force-pushed the worktree-agent-ab0fc502ce6a6c03b branch from cdfa81f to 524ab18 Compare July 26, 2026 18:06
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