Skip to content

fix(session): stop phantom repeated keystroke replay on reconnect/flap - #295

Draft
tstapler wants to merge 12 commits into
mainfrom
backlog/stapler-squad-phantom-keystroke-replay-v2
Draft

fix(session): stop phantom repeated keystroke replay on reconnect/flap#295
tstapler wants to merge 12 commits into
mainfrom
backlog/stapler-squad-phantom-keystroke-replay-v2

Conversation

@tstapler

@tstapler tstapler commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a bug where a single keystroke (observed as 1) got repeatedly
re-delivered to the agent/tmux session while a session's connection was
flapping (connect → "session not started or paused" → reconnect), making
the session unusable.

Two independent, previously-untested code paths contributed:

  1. Server-side: SessionDriver's startup-dialog auto-answer
    (isStartupDialog/shouldApprovePromptSendKeys("1\n")) had no
    bound on how many times it could resend the same answer while the PTY
    buffer looked "stuck" (a real flap) or kept growing with unrelated new
    output (an ordinary, non-flapping session).
  2. Client-side: useTerminalStream's input path had no guard against
    replaying/leaking buffered input across a superseded connection
    generation during a reconnect.

Supersedes #288, which was opened from a branch built on a badly stale
local main (~1429 commits behind origin/main), making its diff show
2769 unrelated files. This PR is the same 12 commits, cherry-picked onto a
fresh branch off current origin/main.

What Changed

  • session/session_driver.go: added a hash-based DialogAnswerLatch
    (answerDialogOnce, maxDialogAnswerAttempts=3) that tail-slices the PTY
    buffer before matching/hashing so resends are bounded both during a real
    stuck-buffer flap and during ordinary active-session output growth.
    Rebasing onto origin/main surfaced that main had independently added
    its own simpler fix for the same symptom (a dialogAwaitingClear bool);
    this PR's tail-sliced, bounded-retry latch fully supersedes it, since
    main's version still read the raw (non-tail-sliced) PTY buffer and would
    reproduce the original bug in an ordinary, non-flapping session once new
    output pushed the dialog text out of a tailed window.
  • session/instance_state.go: GetEffectiveStatus()'s data race is
    fixed differently than originally — origin/main had independently
    fixed the same underlying race via a lock-free Instance.Snapshot()
    copy-on-write pattern in the interim, which this PR now uses instead of
    a manual RLock.
  • server/services/connectrpc_websocket.go: extracted the input
    read-goroutine (runInputReadLoop) with a bounded, prompt exit on
    connection close. origin/main independently added an unrelated new
    streamShellViaControlMode feature (shell-tab streaming) in the same
    file; both coexist untouched.
  • web-app/src/lib/hooks/useTerminalStream.ts: added a
    connection-generation guard so an overlapping/stale connect() or
    disconnect() can't mutate a newer generation's live connection state.
    origin/main had independently grown a substantial new backoff/auto
    -reconnect system (BackoffState, isHardFailed, handleManualReconnect,
    visibility/online reconnect listeners) since this branch's fork point;
    the generation guard is grafted into that newer structure rather than
    replacing it.
  • web-app/src/lib/terminal/MessageQueue.ts + new
    InputDropBadge.tsx / useDropEpisodeCoalescer.ts: input queued
    during a disconnect is dropped (not replayed) when superseded, and the
    user is visibly (badge) and audibly (assertive aria-live announcement)
    signaled when this happens.
  • Regression tests across both stacks (see Test plan), including 4 tests
    rewritten to work with origin/main's new isConnectingRef guard, which
    makes some of the original tests' synchronous overlapping-connect()
    premise unreachable through the public API — see inline comments in
    useTerminalStream.test.ts for the adaptation rationale.

Test plan

  • go build ./... — clean
  • go test ./session/... ./server/... — all green
  • go test -race ./session -run "TestSessionDriver_StuckDialogAnswersBoundedNotUnbounded|TestSessionDriver_TailSliceBoundsDialogMatchAndHash|TestSessionDriver_DialogGaveUp_FallsThroughToInactivityEscalation|TestAnswerDialogOnce" — clean, no races
  • cd web-app && npx tsc --noEmit — clean
  • cd web-app && npx jest --no-coverage — 3675 tests, only 2 pre-existing failures in files this PR never touches (SessionDetail.embedded.test.tsx, BacklogEmptyState.test.tsx — confirmed zero diff via git diff --stat against those paths)
  • make lint — 0 issues
  • make build — web UI + Go binary build successfully
  • Rewrote TestSessionDriver_DialogGaveUp_FallsThroughToInactivityEscalation: the rebase surfaced that its original assertion (waiting for the real 10-minute inactivity timeout) was unreachable in test time even before the rebase (session_driver.go is byte-identical pre/post-rebase) — per plan.md's own anticipated fallback, rewrote it as a narrower, faster unit test on the actual regression surface (the dialogGaveUp control-flow fall-through), confirmed to fail against the old assertion shape and pass with the fix

Closes backlog item 04089969-0f19-499c-be34-2e8bcfc4f13e.

🤖 Generated with Claude Code

tstapler and others added 12 commits August 1, 2026 11:18
…e evidence

Research phase (6 parallel agents) plus direct code reading found the
originally-suspected client MessageQueue/reconnect duplication does not
explain repeated single-keystroke delivery (queue items are consumed
exactly once; server input relay sends each message at most once).

session/session_driver.go's startup-dialog auto-answer loop is the
confirmed primary cause: it polls Preview() every 2s and resends
SendKeys("1\n") with no de-duplication/backoff whenever isStartupDialog()
matches, independent of the auto_yes flag. A live-executing test
(session/phase0_repro_test.go) reproduces 3 repeated SendKeys("1\n")
calls when Preview() returns stalled dialog content, matching the
ticket's "over and over" symptom.

The client MessageQueue/epoch-guard gap is real but secondary (per AC3's
explicit text) and will be fixed as additive hardening.
…ial review

Plan covers two additive fixes: a content-hash latch on session_driver.go's
startup-dialog auto-answer loop (the confirmed root cause), and a connection-
generation guard + drop-on-close fix for MessageQueue/useTerminalStream
(AC3's explicit secondary hardening requirement), plus an InputDropBadge
UX surface and the Go/Jest regression tests required by AC4.

Adversarial review caught two real blockers before any code was written:
the fix's control-flow could silently starve the driver's inactivity-
timeout/ReviewQueue escalation once the dialog latch gave up, and the
content-hash approach was vulnerable to incidental formatting jitter
(same failure class as the adjacent #164 resize-loop bug). Both resolved
and re-verified; architecture and UX reviews landed at CONCERNS only,
folded into the plan directly.
Validation phase (3 parallel subagents: test-suite design, pre-mortem,
cross-artifact consistency) surfaced one new BLOCKER and one new P1 the
Phase 3 review rounds missed:

- BLOCKER: no test actually exercised AC4's literal "queued-message-drop-
  on-close interleaving" scenario (Task 2.1.2 was an isolated close()
  unit test, not a live-reconnect interleaving test). Added Task 2.2.8.
- P1: Preview() returns the entire accumulated PTY buffer, not a tailed
  "current screen" snapshot, so the content-hash latch's whitespace-only
  normalization would still misfire in ordinary, non-flapping sessions
  once any new output changed the whole-buffer hash - a materially larger
  regression surface than the formatting-jitter case already closed. Task
  1.1.2 revised to tail-slice via the existing tailContent/
  statusDetectionTailBytes precedent before matching and hashing.

Also closed two coverage gaps the validation subagent flagged (a test
proving the control-flow fall-through reaches escalation, and a missing
Playwright e2e spec required by this repo's feature-registry convention).
Readiness gate: PASS (6/6 ACs covered, 0 open blockers/P1s, ADR-001 on
disk, no schema changes).
… reconnect input path

Fixes the confirmed root cause of the phantom repeated "1" keystroke bug
(backlog 04089969-0f19-499c-be34-2e8bcfc4f13e): session_driver.go's
startup-dialog auto-answer loop resent SendKeys("1\n") every poll tick
with no de-duplication whenever isStartupDialog() matched, and Preview()
returns the entire accumulated PTY buffer rather than a tailed snapshot,
so the resend could recur in ordinary non-flapping sessions too, not
just during a connection flap.

- session/session_driver.go: content-hash latch (dialogUnanswered ->
  dialogAwaitingDismissal -> dialogGaveUp), tail-sliced + whitespace-
  normalized before hashing/matching (via the existing tailContent/
  statusDetectionTailBytes precedent), bounded retry-on-failure, and a
  control-flow fix so a GaveUp/AwaitingDismissal latch falls through to
  the existing inactivity-timeout/ReviewQueue escalation instead of
  silently wedging the driver loop. Applied to both SendKeys("1\n")
  call sites (startup dialog + approval prompt).

- server/services/connectrpc_websocket.go: extracted the WebSocket
  input-read loop into runInputReadLoop so it's independently testable
  (bounded-exit test proves it stops forwarding input promptly once the
  connection closes).

- web-app MessageQueue/useTerminalStream: close() now drops (does not
  drain) buffered-but-unsent input; useTerminalStream gained a
  connection-generation guard (mirroring the existing usePathCompletions
  generation-counter idiom) so an overlapping/rapid reconnect can't leave
  two live message loops fighting over the same session. Dropped input
  now surfaces via a new InputDropBadge (assertive live-region
  announcement + visible badge, coalesced per drop episode).

Confirmed via a live-executing runtime test (session/phase0_repro_test.go,
now superseded by permanent regression coverage in session_driver_test.go)
that the unfixed driver resent the keystroke repeatedly against a stalled
preview buffer. Full adversarial/architecture review + pre-mortem process
in project_plans/phantom-keystroke-replay/ caught and fixed two design
blockers and one P1 gap before this code was written.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q2aGuq28JKW6fT6w2MiKbV
…titute evidence

Task 4.1.1's live-browser repro procedure targets the omnibar session-
creation path, which never attaches a SessionDriver at all (only
CreateDirectorySession/MCP create_session do) - the exact code this fix
touches can never run through that path. Also found /tmp is globally
pre-trusted on this machine (defeats the trust-dialog trigger) and a
log-routing gap in isolated STAPLER_SQUAD_INSTANCE deployments unrelated
to this fix. Documented all three for a follow-up live verification pass;
recorded the automated re-run of Epic 1's regression suite (same Phase 0
methodology, now bounded instead of unbounded) as substitute evidence.
MUST FIX: InputDropBadge's manual dedup ref broke under React StrictMode
(setup->cleanup->setup replay could clear the dismiss timer without
rearming it, leaving the badge visible forever on first mount in dev).
Collapsed into a single effect with its own cleanup, no ref-based dedup.

CONCERN: window.__e2eTriggerInputDropped now gates on NODE_ENV so it's
tree-shaken out of production builds instead of shipping as an
always-present, unauthenticated script-callable surface.

Also: exhaustive switch (with a panic default) on dialogLatchStatus so a
future 4th status can't silently fall through the unbounded-resend path;
hoisted the duplicated SendKeys("1\n") closure shared by both latch call
sites; removed a redundant useCallback wrapper in TerminalOutput.tsx;
documented the onScrollbackRequest plan deviation.

Reverted one over-eager cleanup: moving all tail-slicing to the call
site (instead of inside answerDialogOnce) broke TestAnswerDialogOnce's
growing-buffer test cases (f)/(g), which call answerDialogOnce directly
with untailed input and rely on its own internal tailContent call.
Restored the internal tail-slice - the "duplication" with the call
site's own tailed value (needed separately for isStartupDialog/
shouldApprovePrompt) is cheap and was never actually redundant once a
unit test exercises the function in isolation.

All Go and Jest suites verified green after this pass (full session +
server/services test trees, plus TerminalOutput's existing suites).
…ager stubs

session_driver_test.go's stuckDialogProcessManager fake intentionally
returns (nil, nil) for GetPTY/Attach since neither is exercised by the
dialog-latch tests. Matches the existing nolint:nilnil convention used
in history_detector.go for the same real-fake pattern.
…n sdd:6-verify review

Two independent parallel reviews (Go idiom/concurrency, React/TS idiom)
each surfaced one real MUST FIX:

- GetEffectiveStatus() claimed (via its own comment and callers' comments)
  to acquire stateMutex.RLock but never did, and the new session_driver_test.go
  tests wrote inst.Status directly from a second goroutine while the driver
  loop read it via GetEffectiveStatus — a genuine data race caught by
  `go test -race`. Fixed by actually taking the RLock in GetEffectiveStatus
  (matching its documented contract) and routing the tests' cleanup writes
  through stateMutex.Lock().

- useTerminalStream's disconnect() had no connection-generation guard on its
  delayed (1000ms) graceful-close timeout or its trailing setIsConnected(false),
  unlike connect()'s read loop. A disconnect() that armed its timer while
  still connected, raced by a newer connect() that took over in the
  meantime, would abort/null the *newer* generation's AbortController and
  clobber its isConnected state once the stale timer fired — the same class
  of bug this whole fix exists to close. Fixed by capturing the generation
  at disconnect() entry and gating both effects on it; new regression test
  confirmed to fail against the pre-fix code and pass against the fix.

Also addressed two Go nitpicks from the same review: replaced the bespoke
errSentinel type with errors.New, and removed a local min() helper that
redundantly shadowed the Go 1.21+ builtin.

go test -race ./session ./server/services and the full web-app Jest suite
are green.
…and fixes

Documents the two real MUST FIX findings (Go data race, disconnect()
reconnect-race gap) surfaced by a fresh two-agent idiom review pass and
fixed in f3a876f, per plan.md's existing Post-Implementation Finding
convention.
… MCP create_session

Completes the literal live-browser-equivalent repro the earlier
Post-Implementation Finding could only substitute with re-run unit tests.
Created a real session via mcp__stapler-squad__create_session (the path
that actually exercises StartSessionDriver), induced the ticket's exact
"session not started or paused" flap via pause/resume immediately after
creation, and confirmed via server logs the startup dialog was answered
exactly once inside the flap window with no resend and no repeated "1" in
the final recovered terminal state.
…tion test for rebased base

Rebasing these 12 commits onto current origin/main surfaced two issues in
TestSessionDriver_DialogGaveUp_FallsThroughToInactivityEscalation that were
latent even before the rebase (session_driver.go itself is byte-identical
to the pre-rebase version — confirmed via diff):

1. The driver's activityRef logic always prefers the *later* of
   LastMeaningfulOutput and initialPromptSentAt as the inactivity
   reference. Once the dialogGaveUp fall-through reaches the initial-prompt
   -send step, initialPromptSentAt becomes "now" and permanently wins over
   any artificially-stale LastMeaningfulOutput the test seeds — so the real
   10-minute driverInactivityTimeout can never be reached in test time.
2. The initial-prompt-send step itself is gated on claudeAtPrompt (never
   true against this test's static fake pane content) or timedOut, which
   only becomes true after driverReadyTimeout (30s) — a wait the test
   didn't budget for.

Per plan.md's own anticipated fallback for this exact test ("a narrower
unit test on the post-latch branch logic if a full-duration ticker test
proves impractical"), rewrote the test to prove the actual regression
surface directly: dialogGaveUp's fall-through reaches the code *after* the
dialog-answer branch (proven by the initial prompt's SendKeys call actually
firing) rather than being trapped in the `continue` Blocker 1 exists to
close, instead of waiting on the unreachable full inactivity-timeout path.

Verified: fails against the old assertion shape when run standalone,
passes with the rewrite; go test -race ./session clean; make lint clean.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 117 feature files to /tmp/tmp.tYkakDbzNK/backend
Wrote 15 feature files to /tmp/tmp.tYkakDbzNK/backend
Wrote 45 feature files to /tmp/tmp.tYkakDbzNK/backend
Wrote 7 feature files to /tmp/tmp.tYkakDbzNK/backend
Wrote 12 feature files to /tmp/tmp.tYkakDbzNK/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 22/180 features have testIds (12.2%)

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

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:97: missing iteration count
benchmarks/go/tier1-baseline.txt:196: missing iteration count
tier1-bench.txt:96: missing iteration count
tier1-bench.txt:195: missing iteration count
goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/session
cpu: INTEL(R) XEON(R) PLATINUM 8573C
                                            │ benchmarks/go/tier1-baseline.txt │
                                            │              sec/op              │
CircularBufferWrite_4KB-4                                          189.5n ± 2%
CircularBufferWrite_4KB_Allocs-4                                   189.6n ± 2%
CircularBufferGetRecent_4KB-4                                      564.1n ± 4%
CircularBufferGetAll-4                                             4.228µ ± 3%
GetTimeSinceLastMeaningfulOutput_HotPath-4                         58.97n ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        30.22n ± 0%
geomean                                                            231.2n

                                            │ 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                           20.13Gi ± 1%
CircularBufferGetRecent_4KB-4                       6.762Gi ± 4%
geomean                                             11.67Gi

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                            │ tier1-bench.txt │
                                            │     sec/op      │
CircularBufferWrite_4KB-4                        185.6n ± 14%
CircularBufferWrite_4KB_Allocs-4                 193.5n ±  8%
CircularBufferGetRecent_4KB-4                    633.5n ±  8%
CircularBufferGetAll-4                           4.504µ ±  4%
GetTimeSinceLastMeaningfulOutput_HotPath-4       50.19n ±  0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      25.66n ±  1%
geomean                                          225.6n

                                            │ 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         20.56Gi ± 12%
CircularBufferGetRecent_4KB-4     6.021Gi ±  7%
geomean                           11.13Gi

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
cpu: INTEL(R) XEON(R) PLATINUM 8573C
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
StripANSI_PlainText-4                                5.923n ± 3%
StripANSI_WithEscapes-4                              654.2n ± 0%
ProcessOutput_InactiveState-4                        17.99n ± 0%
geomean                                              41.16n

                              │ 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: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                              │ tier1-bench.txt │
                              │     sec/op      │
StripANSI_PlainText-4               6.827n ± 0%
StripANSI_WithEscapes-4             700.3n ± 1%
ProcessOutput_InactiveState-4       17.03n ± 0%
geomean                             43.34n

                              │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
ReviewQueue_ConcurrentReads-4                        118.4n ± 2%
ReviewQueue_Add-4                                    481.6n ± 1%
geomean                                              238.7n

                              │ 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: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                              │ tier1-bench.txt │
                              │     sec/op      │
ReviewQueue_ConcurrentReads-4       81.34n ± 3%
ReviewQueue_Add-4                   480.8n ± 1%
geomean                             197.8n

                              │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                                      │ benchmarks/go/tier1-baseline.txt │
                                      │              sec/op              │
CircularBuffer_ConcurrentReadWrite-4                         3.384µ ± 2%
CircularBuffer_BurstAppend-4                                 126.1µ ± 0%
CircularBuffer_GetLastN_LargeBuffer-4                        18.79µ ± 1%
CircularBuffer_GetRange_Sequential-4                         10.05µ ± 2%
CircularBufferAppend-4                                       123.3n ± 0%
CircularBufferGetLastN-4                                     2.246µ ± 1%
CircularBufferConcurrentAppend-4                             176.8n ± 1%
geomean                                                      3.264µ

                                      │ 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                       484.1Mi ± 0%

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                      │ tier1-bench.txt │
                                      │     sec/op      │
CircularBuffer_ConcurrentReadWrite-4        3.311µ ± 2%
CircularBuffer_BurstAppend-4                113.3µ ± 1%
CircularBuffer_GetLastN_LargeBuffer-4       21.45µ ± 2%
CircularBuffer_GetRange_Sequential-4        10.40µ ± 8%
CircularBufferAppend-4                      108.2n ± 1%
CircularBufferGetLastN-4                    2.296µ ± 1%
CircularBufferConcurrentAppend-4            150.4n ± 1%
geomean                                     3.157µ

                                      │ 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      538.8Mi ± 1%

pkg: github.com/tstapler/stapler-squad/session/tmux
cpu: INTEL(R) XEON(R) PLATINUM 8573C
                             │ benchmarks/go/tier1-baseline.txt │
                             │              sec/op              │
StripANSICodes_PlainText-4                          5.189n ± 0%
StripANSICodes_WithEscapes-4                        610.5n ± 0%
IsBanner_PlainText-4                                431.1n ± 0%
geomean                                             110.9n

                             │ 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: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                             │ tier1-bench.txt │
                             │     sec/op      │
StripANSICodes_PlainText-4         6.882n ± 0%
StripANSICodes_WithEscapes-4       666.8n ± 1%
IsBanner_PlainText-4               456.1n ± 0%
geomean                            127.9n

                             │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                                   │ benchmarks/go/tier1-baseline.txt │
                                   │              sec/op              │
TokenParser_ProcessUserEntry-4                            4.556m ± 1%
DetectCommandsInText/NoSlash-4                            5.151n ± 5%
DetectCommandsInText/WithCommand-4                        1.570µ ± 0%
geomean                                                   3.328µ

                                   │ 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: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                   │ tier1-bench.txt │
                                   │     sec/op      │
TokenParser_ProcessUserEntry-4           4.725m ± 2%
DetectCommandsInText/NoSlash-4           6.630n ± 0%
DetectCommandsInText/WithCommand-4       1.684µ ± 0%
geomean                                  3.750µ

                                   │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                               │ benchmarks/go/tier1-baseline.txt │
                               │              sec/op              │
DiffShortstat/GitVCSReader-4                          2.428m ± 0%
DiffShortstat/GoGitVCSReader-4                        70.61n ± 0%
DiffShortstatCached-4                                 70.51n ± 1%
geomean                                               2.295µ

                               │ benchmarks/go/tier1-baseline.txt │
                               │               B/op               │
DiffShortstat/GitVCSReader-4                       62.56Ki ± 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: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                               │ tier1-bench.txt │
                               │     sec/op      │
DiffShortstat/GitVCSReader-4         2.271m ± 1%
DiffShortstat/GoGitVCSReader-4       59.69n ± 0%
DiffShortstatCached-4                60.50n ± 2%
geomean                              2.016µ

                               │ tier1-bench.txt │
                               │      B/op       │
DiffShortstat/GitVCSReader-4      62.56Ki ± 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 Aug 1, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 9ms (▲ slower +60.1%; baseline: 6ms)
list-sessions-total-mean: 16ms (▲ slower +122.6%; baseline: 7ms)

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 16 KB/s ▼ -0.6% (baseline: 16 KB/s)
terminal-throughput-p50: 16 KB/s ▼ -0.7% (baseline: 16 KB/s)

@github-actions

github-actions Bot commented Aug 1, 2026

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

github-actions Bot commented Aug 1, 2026

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 Aug 1, 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.

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