fix(web): dead-band terminal resize loop, RPC dedup, and WebGL fallback - #272
Merged
tstapler merged 2067 commits intoJul 28, 2026
Conversation
…n is always visible Button was pushed off-screen on narrow viewports due to flex-row layout with flexGrow:1 on the text. Switch to column direction so the button always renders below the error message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…detection Show GitHubBadge inline in SessionRow (row/list view) so PR status is visible without switching to card view. Previously the badge only rendered in SessionCard (card view). Switch getCurrentBranchName from subprocess (git rev-parse) to go-git direct file read — no subprocess overhead. Add exported GetCurrentBranchName wrapper and CurrentBranch() method on Instance that falls back to live git read for directory sessions (Branch field is always empty for non-worktree sessions). Add UpdatePRStatus() helper for atomic in-memory PR status updates from PRStatusPoller. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: repair broken release pipeline and build-from-source path Every GoReleaser release since v1.9.0 has failed with "found 3 builds with the ID 'stapler-squad'" because none of the three build entries in .goreleaser.yaml declared an explicit id, so GoReleaser assigned them all the same default. This is why brew install pulls the ancient 1.9.0 build (Formula/stapler-squad.rb hasn't updated since) and why install.sh's release-asset download has had nothing to fetch for every tag from v1.20.1 through v1.32.0. Give each build block an explicit unique id. Also fixes two things blocking the build-from-source path: - config/executor.go: lookPathOnlyExecutor.Command used a raw exec.Command instead of safeexec.CommandContext, tripping the norawexec custom lint rule and failing `make build` outright. - Makefile: `go build` never set the version ldflag, so both `make build` and plain `go build .` reported the stale hardcoded "1.1.2" regardless of what was actually built. Derive VERSION from `git describe` and pass it via -ldflags, matching what GoReleaser already does for tagged releases. Verified locally: `make build` now succeeds end-to-end and `./stapler-squad version` reports the real git-described version. `goreleaser check` and a full `goreleaser release --snapshot --clean` (with the GITHUB_* env vars CI provides) both succeed, including Homebrew formula generation. Fixes #143 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: isolate TestGetConfigDir from ambient STAPLER_SQUAD_* env vars GetConfigDir() checks STAPLER_SQUAD_TEST_DIR and STAPLER_SQUAD_INSTANCE before falling through to test-mode auto-detection. When the test process inherits either from its environment (e.g. running inside a stapler-squad-managed session), the "uses test mode isolation for tests" subtest short-circuits on the ambient value instead of exercising auto-detection, and fails. Clear both for the duration of the subtest and restore them afterward. Verified with `go test ./config/... -run TestGetConfigDir -count=3` and a full `go test ./config/... -count=1`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: sanitize VERSION and wire it into build-embedded too Code review on this branch surfaced two real gaps in the version-ldflag fix: 1. Security: git tag names may legally contain shell metacharacters (backtick, $()). Make's $(VERSION) substitution is pure text substitution done before the shell parses the recipe line, so those characters land as live shell syntax inside the double-quoted `-ldflags` argument — anyone who can get a maliciously-tagged ref fetched into a checkout gets command execution on `make build` / `make install-service`. Strip VERSION to a safe charset before it ever reaches the shell. (Checked whether the analogous `VERSION=$(git describe ...)` in .github/workflows/build.yml has the same problem: it doesn't. That's a bash variable expansion of an already-computed string, not a macro substitution before the shell parses the command — bash does not re-evaluate `$()`/backticks embedded in an expanded variable's value. Verified empirically. Left that file alone.) 2. Completeness: `build-embedded` (the tmux-bundled single-binary target used by `make build-tmux` -> `make build-embedded`) builds the same stapler-squad binary as the primary `stapler-squad` target but wasn't wired to the new LDFLAGS, so it would have kept shipping the exact stale "1.1.2" version string issue #143 complains about. Verified: `make build` still succeeds and reports a correct, sanitized version. `make -n build-embedded` confirms the ldflags now appear in that target's go build invocation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * ci: add goreleaser check as a regression guard for .goreleaser.yaml The build-ID collision this PR fixes broke every release for 15+ months with zero visibility: the only place it ever surfaced was a failed Action run on a tag push (release.yml only runs `goreleaser release` on `push: tags: v*`), which nobody was watching closely enough to catch. Add a small, fast, dedicated workflow that runs `goreleaser check` on every change to .goreleaser.yaml, so a config mistake like this one fails a PR check immediately instead of silently breaking every subsequent release. `goreleaser check` also fails non-zero for known-but-accepted deprecation warnings, not just genuine invalidity, so a naive `args: check` step would have gone red on day one against this repo's existing config (it still uses the classic `brews` publisher, which GoReleaser wants migrated to `homebrew_casks` — a real behavioral change for end users, not a syntax rename: casks use different install semantics, code-signing/Gatekeeper expectations, and app-bundle lifecycle hooks that don't apply to a plain CLI binary, and would very likely break the `brew install` command this repo's README documents. That migration needs its own careful, tested PR, not a blind swap bundled into an install-bug fix). Fixed the two safe, pure-syntax deprecations in the same commit (`archives.format`/ `format_overrides.format` -> `formats`, now a list — verified via a full snapshot build that archive naming/extension per-OS is unchanged) and left `brews` alone. The new workflow's check step distinguishes "configuration is invalid" (hard fail) from "valid, but uses deprecated properties" (pass, tracked separately) by output content rather than exit code, so it stays a real regression guard instead of either being permanently red on accepted debt or silently disabled. Verified locally: - `goreleaser check` on the current config: valid, only the accepted `brews` deprecation remains. - Simulated the exact original bug (duplicate build ids) against a scratch copy of the config: the same check logic correctly reports "configuration is invalid" and would fail CI. - Full `goreleaser release --snapshot --clean` still succeeds end-to-end after the formats-list migration, archive names/ extensions unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: sync registry validation with github_user.proto and add missing feature files CI's Registry Validation check was failing on this PR (unrelated to the actual fix, but blocking it from going green): `tools/scanner/validate-registry.sh` never scans `proto/session/v1/github_user.proto`, even though the Makefile's `registry-generate-backend` target does. Both were last touched independently, and the validation script's hardcoded proto list was never updated when github_user.proto's RPCs (added in 3be7e09, well before this branch existed) were registered. The result: `docs/registry/features/backend/*.json` never had entries for ListGitHubAccounts/PollGitHubDeviceAuth/RevokeGitHubToken/ StartGitHubDeviceAuth, and the validation script would report them as "Removed RPCs" (154 committed vs. 147 generated, 4.55% divergence) forever, regardless of whether the per-feature files existed — the scanner it runs simply never looks at that proto file. - Added the missing `github_user.proto` scan step to validate-registry.sh, matching the Makefile. - Ran `make registry-generate` to create the 4 missing per-feature JSON files these RPCs were always supposed to have. Verified: `./tools/scanner/validate-registry.sh` now reports "Committed: 154 Generated: 154 Divergence: 0.0%" and exits 0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…rsing crash) (#150) * fix: shell-quote claude launch args to stop injection and flag-parsing crash Backlog/triage spawned sessions died on launch: the prompt is interpolated into a shell command (tmux launches programs through a shell), and Go's %q produces double quotes, which do not suppress backtick/$(...)/$VAR expansion. Backlog prompts are full of backtick-wrapped tokens (`/backlog/done-N`, etc.), so the shell executed each as a command instead of passing it to claude. Separately, backlog prompts begin with "--- BACKLOG ITEM DATA ---", which claude's arg parser rejected as an unrecognized flag once quoting was fixed. Add shellQuote (POSIX single-quoting, the same style already used for --mcp-config) and apply it to every claude flag value that gets interpolated into the shell command: --append-system-prompt, --allowedTools, --permission-mode, and the positional prompt. Insert a bare "--" before the prompt so a leading "--" in the prompt text is treated as data, not flags. Verified against the real claude CLI that both -- as an end-of-options separator and --append-system-prompt-file are accepted, and confirmed via a real shell execution that a $(...) payload in a backlog-shaped prompt no longer executes. Fixes #148 * fix: close remaining shell-injection gaps found by review Multi-agent review of the shellQuote fix found the same vulnerability class still present two call sites over: - --resume value: claudeSessionID traces back to the client-supplied resume_id field on CreateSessionRequest with no format validation, and was still interpolated unquoted into the shell-executed launch command in the same function that was just patched. - claudeMCPConfigFlag hand-rolled its own shell single-quoting (a literal '...' wrapper) instead of reusing shellQuote, leaving a second, untested implementation of the same job living next to the new one. Not currently exploitable (MCPServerURL/UUID aren't attacker-supplied today) but a latent gap in the same file that just added the primitive meant to prevent this. Also add regression tests the review flagged as missing: --allowedTools and --permission-mode had zero shell-safety coverage even though shellQuote was applied to both, so a partial revert of just those two lines would have passed the full suite silently. Reworked the two existing Prompt/AppendSystemPrompt regression tests to assert against hand-written expected literals instead of calling shellQuote() again, so they don't just verify the function against itself. Added only-single-quote, embedded-newline, and combined backtick+quote cases to TestShellQuote's table. Confirmed session/claude_command_builder.go's separate --resume path is not affected: it validates the session ID against a strict UUID v4 regex before use, and is not wired into any production call site today. --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com>
…detection (#149) * fix(analytics): escape analytics session_id mismatch and dead mangle detection Escape event rows were tagged with the tmux session name instead of the stable session UUID, so the web UI (which queries by stable UUID) never found any data even though capture itself was working (185K+ rows in the live DB). Mangle detection was fully implemented and unit-tested but never wired into production — SetCorrelator was never called, emitEventWithStageAndSeq always recorded Stage 1 observations instead of checking Stage 2 against them, and the Stage 2 tap computed session_seq from the wrong buffer offset. - Thread instance.GetStableID() into the escape parser via a new ResponseStream.SetStableSessionID, scoped narrowly so cc.sessionName's other use sites (PTY naming, persistence dirs, rate limiting) are untouched - Wire MangleCorrelator per-parser with its eviction loop tied to stream lifetime; branch RecordStage1 vs CheckStage2 by stage instead of always recording - Fix Stage 2 session_seq to use the coalesced frame's start offset, not its end offset, so it aligns with Stage 1's numbering - Convert totalSequences/totalMangled to atomic.Int64 (both stages write through the same parser instance from different goroutines) - Mirror escape analytics defaults into DefaultConfig() to match LoadConfigFromPath, per the existing "must mirror" comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(analytics): redesign mangle correlation to be offset-independent Code review on PR #149 found the byte-offset arithmetic fix for Stage 2 correlation couldn't work regardless of the arithmetic: streamViaControlMode's data comes from a separate tmux control-mode client, not the same producer as Stage 1's raw PTY read, so the two sides have no shared byte-offset numbering. Verified empirically (live tmux experiment with two simultaneous client attachments) that the two streams carry identical content in the same order, just offset by a constant that resets on each client's own connect/resize redraw — a calibration problem, not a content mismatch. Redesigned MangleCorrelator to correlate ordinally per (session, sequence type) instead of by byte position, which is robust to that offset entirely. Also addresses the review's MAJOR findings: sessionID is now atomic.Pointer[string] instead of an unsynchronized plain string; the parser setter is renamed SetStableSessionID to stop colliding with a tmux-name-keyed SetSessionID called 4 lines away; the correlator eviction goroutine is now tracked by ResponseStream's WaitGroup (so Stop() actually blocks on it) and panic-recovered; and the production wiring line in ClaudeController.Start() now has a test that would catch a regression back to the tmux name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: restore .claude/scheduled_tasks.lock accidentally deleted in prior commit Unrelated to this PR's changes — an environment-local lock file got staged as deleted before this session started and was swept up by a non-path-scoped git commit. Restoring it to match origin/main. --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… related bugs (#152) * fix(backlog): resolve GitHub URLs in repo path, add first-visit tour The Repository Path field silently accepted a GitHub URL and used it verbatim as a filesystem path, producing garbage paths and silent triage failures (stapler-squad#148's "Related" section). It also had no guidance on what it expected, so users had no way to know a URL wasn't a valid local path. - BacklogService now resolves GitHub URLs/shorthand in repo_path to a local clone (same machinery CreateSession already uses for the Omnibar), or returns a clear validation error instead of storing garbage. Covers both CreateBacklogItem and the UpdateBacklogItem fix-up path. - RepoPathInput gained an optional hint line and live GitHub-URL detection ("Will clone owner/repo to ~/.stapler-squad/repos/..."). - BacklogItemForm explains the two previously-unlabeled checkboxes and shows "Cloning repository…" while a fresh clone is in flight. - New BacklogTourModal walks first-time visitors through the item lifecycle, the repo-path gotcha explicitly, and what the skip flags do; reopenable via a "?" button in the page header. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: shell-quote claude launch prompts, stop triage poll from losing edits Two backlog-adjacent bugs Carl filed while debugging the repo-path issue above: - stapler-squad#148: backlog/triage session prompts were interpolated into the shell command with Go's %q (double quotes), so backtick- wrapped tokens and $(...) in the auto-generated prompt were executed by the shell, and a leading "--" was parsed as a claude CLI flag — spawned sessions died on launch. Now single-quoted (shellQuote, which suppresses all shell expansion) with a "--" separator before the prompt. - stapler-squad#146: BacklogItemDetail's full-screen loading guard unmounted <BacklogItemForm> on every 5s triage-status poll, so any unsaved acceptance criteria typed during triage were silently discarded. The loader now only shows on the initial load, and the poll is suspended while the edit form is open. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(e2e): install missing test deps, extend server-boot timeout allure-playwright (declared in package.json) was missing from the committed node_modules, breaking `npx playwright test` outright. Installed it and its transitive deps. Also bumped the test-server health-check timeout from 30s to 90s: a cold test-mode boot (DB init + demo seeding) was observed taking ~30-45s before /health responds, right at the old cap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * revert(e2e): don't commit node_modules lock manifest without the packages The previous commit updated .package-lock.json (npm's per-tree manifest) after `npm install` pulled in allure-playwright and ~350 transitive deps, but those package directories are gitignored and weren't force-added — committing just the manifest without the actual files would claim the tree is in sync when it isn't. tests/e2e/node_modules is vendored (git-tracked despite .gitignore), so fully fixing the missing-dependency gap means force-adding ~thousands of new files, which is out of scope for this PR. Leaving the test-server.ts timeout bump from the previous commit in place since that's independently correct; flagging the vendoring gap separately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address code review findings (shell-quote gap, tour checkbox bug, path traversal) Multi-dimension code review (Testing, Code Quality, Architecture, Security) on PR #152 surfaced two CRITICALs, both cross-validated by 2-3 independent reviewers, plus several MAJOR issues: - CRITICAL: AllowedTools/PermissionMode in instance_tmux.go still used Go's %q instead of the new shellQuote — the exact same shell-injection class this PR fixes for AppendSystemPrompt/Prompt, just on two sibling fields populated directly from client RPC input. - CRITICAL: BacklogTourModal's "Don't show this again" checkbox was a no-op — onClose (mapped to setTourComplete) unconditionally persisted onboarded=true regardless of the checkbox state. Fixed by changing the modal's callback contract to onComplete(persist: boolean), with a new hideTour() on the hook for the non-persisting path. - MAJOR (security): GitHub owner/repo regexes in repo_path.go didn't reject "." / ".." segments, so a crafted repo_path could resolve the clone directory outside ~/.stapler-squad/repos/github.com/. Added an isTraversalSegment guard across all 4 parse branches. - MAJOR: hardcoded 24px margin replaced with the vars.space token; extracted BacklogTourModal's reused modal-chrome styles out of OnboardingModal's own CSS module into a new shared components/ui/ModalTour.css.ts (OnboardingModal re-exports from it, so OnboardingModal.tsx needed no changes). - MAJOR (testing): replaced two circular shellQuote()-derived test oracles with hardcoded literals, added a message-content assertion the Update-path resolver-error test was missing, and strengthened the poll-suspended-while- editing test to assert the actual unsaved acceptance criterion survives rather than just checking a fetch call count. Deferred (documented, not blocking): DRY duplication between backlog_service.go/session_service.go's GitHub resolution, a matching hardcoded path format in the frontend hint text, and the pre-existing synchronous-clone-in-RPC-handler pattern this PR extends to a second call site (mirrors existing CreateSession behavior). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
#155) * fix: make web-build generate proto bindings on a fresh clone `make web-build` builds `web-app/out` without depending on `proto-gen`, so a clean checkout fails with "Module not found: '@/gen/session/v1/session_pb'" because the TypeScript protobuf bindings were never generated. `make build` was unaffected since it lists `proto-gen` as a direct prerequisite of the top-level target. Add `proto-gen` as a prerequisite of `web-app/out` so the TS bindings exist before the Next.js build runs, regardless of which entry point is used. `proto-gen` is a no-op when the bindings are already up to date, so this doesn't slow down repeat builds. Fixes #144 (Bug 1). Bug 2 (go-m1cpu SIGSEGV) is already resolved — the repo depends on gopsutil/v4, which dropped the go-m1cpu cgo dependency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test: add CI smoke test for standalone `make web-build` The existing CI pipeline never exercises the Makefile's own dependency graph: `.github/actions/prepare` hand-runs `buf generate` and `pnpm run build` directly, bypassing `make` entirely. That's exactly why the missing `proto-gen` prerequisite on `web-app/out` (previous commit, fixes #144) went undetected - no CI job ever invoked `make web-build` or `make build` as a fresh clone would. Add a standalone job that checks out cleanly (no shared artifacts, no manual buf/pnpm pre-steps) and runs `make web-build` directly, then asserts the generated TS proto bindings exist. Verified this job's steps fail against the pre-fix Makefile with the exact reported error ("Module not found: '@/gen/session/v1/session_pb'") and pass against the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: untrack stale generated proto files that were force-committed gen/, web-app/src/gen/, and .proto-gen.stamp are already in .gitignore, but 19 generated files under gen/proto/go/session/v1/ and web-app/src/gen/session/v1/ were force-committed into git anyway (going back through at least PR #60, #51, #54) and never cleaned up. The tracked set was also incomplete/stale - e.g. session.pb.go and session_pb.ts (generated from session.proto, the largest proto file) were never committed at all, while sessionv1connect/session.connect.go (which references types defined in session.pb.go) was. This is exactly what produced the "undefined: v1.CreateSessionRequest" compile errors and "Module not found '@/gen/session/v1/session_pb'" webpack errors in #144 on any workflow that skipped `proto-gen` - the stale committed files gave inconsistent partial signals instead of a clean "not generated yet" failure. `git rm --cached` only removes them from the index; the working-tree copies (freshly regenerated by `make web-build` in the previous commits) are untouched, and .gitignore now actually takes effect for this tree going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…-terminal-resize-loop-fix # Conflicts: # server/dependencies.go # server/server.go # server/services/workspace_service.go # session/capture_test.go # session/history_linker.go # session/history_linker_test.go # session/instance.go # session/tmux/tmux.go # session/tmux/tmux_test.go # web-app/jest.config.js # web-app/jest.setup.js # web-app/package-lock.json # web-app/package.json # web-app/src/app/page.tsx # web-app/src/components/sessions/SessionCard.module.css # web-app/src/components/sessions/SessionCard.tsx # web-app/src/components/sessions/SessionList.tsx # web-app/src/components/sessions/TerminalOutput.tsx # web-app/src/components/sessions/XtermTerminal.tsx # web-app/src/components/sessions/__tests__/XtermTerminal.test.tsx # web-app/src/lib/hooks/useSessionService.ts # web-app/src/lib/hooks/useTerminalFlowControl.ts # web-app/src/lib/hooks/useTerminalStream.ts
Fixes surfaced by running the merged test suite after merging origin/main into the terminal-resize-fit-loop branch (commit 65afd7b): - XtermTerminal.tsx: remove the reintroduced synchronous initial onResize() call. Upstream deliberately deleted this (commit acb0750, "prevent premature resize from corrupting dimension cache") because it fired onResize(80,24) with xterm's construction-time defaults before fitAddon.fit() ever ran, corrupting TerminalOutput's dimension cache. Re-adding it during the merge reintroduced that exact regression, caught by the upstream regression suite XtermTerminalBug.test.tsx ("Bug 1"). - XtermTerminal.test.tsx: add a buffer stub to MockTerminal (origin's new updateScrollbar() reads buffer.active unconditionally), stub a minimal WebGL2RenderingContext global (jsdom has none, and the merged component now gates its WebGL load behind `typeof WebGL2RenderingContext !== 'undefined'` per xterm.js issue #2033), flush the dynamic import('@xterm/addon-webgl') microtask chain after mount, and recalibrate the resize-sampler test helpers' fake-timer advances for the flat 150ms debounce (replacing the old adaptive 10ms-for-first-3 debounce) instead of the old adaptive timing. - TerminalOutput.test.tsx: add the supporting context/hook mocks (AnalyticsContext, ApprovalsContext, useHandedness, TerminalStreamManager, etc.) TerminalOutput now depends on, update the Fit button's aria-label and account for it living behind the (now-collapsible) toolbar toggle, and flush the XtermTerminal lazy-import Suspense boundary plus the new 250ms post-connection resize delay before asserting. - BacklogItemForm.test.tsx: fix a pre-existing tsc error (unrelated to this merge, inherited from origin/main) by typing the onSubmit mock instead of letting it infer a zero-arg jest.Mock. - pnpm-lock.yaml: regenerated after adding @xterm/addon-canvas back to package.json (needed for the Canvas-fallback feature; origin/main's dependency bump to xterm.js 6.x never had this addon since upstream lacks the WebGL->Canvas fallback feature). All fixes verified with `go build ./...`, `go test ./...`, `npx tsc --noEmit`, and the full `npx jest` suite (2840 passing; 3 pre-existing failures on origin/main unrelated to this merge — SessionCard.click.test.tsx and SessionCard.approval-suppression.test.tsx are missing an AnalyticsContext mock that predates this branch, and BacklogEmptyState.test.tsx has an unhandled promise rejection from mockRejectedValue, also pre-existing). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSdgF9zfk4PcYuQE61ooHi
… in code review
- useTerminalFlowControl.ts: cancel any pending deferred resize timer
unconditionally BEFORE the value-dedup early-return in resize(), so a
bounce-back call (A -> B deferred -> back to A) can no longer dedup-return
while leaving a stale deferred send for B still scheduled. Added a
regression test reproducing the exact bounce-back scenario.
- XtermTerminal.tsx: guard the async @xterm/addon-webgl import with a
`cancelled` flag set in the effect's unmount cleanup, preventing
loadAddon() from running against an already-disposed terminal and leaking
an orphaned WebGL context on a fast session-switch unmount.
- useTerminalFlowControl.test.ts: remove dead duplicate jest.mock('@bufbuild/protobuf', ...)
merge-conflict leftover.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSdgF9zfk4PcYuQE61ooHi
…-terminal-resize-loop-fix
triggerCanvasFallback() lacked the same cancelled-guard the async WebGL loader uses, so a post-unmount onContextLoss could call loadAddon() on a disposed terminal (caught but logged spuriously). The post-resize currentPaneRequest timer in useTerminalFlowControl was also never tracked or cleared on unmount, unlike its sibling pendingResizeTimerRef. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HafE8LJ1P3qsX6TokT9Fc
…-terminal-resize-loop-fix # Conflicts: # .claude/commands/perf/make-it-faster.md # .claude/docs/codesigning.md # .github/workflows/benchmark.yml # .github/workflows/build.yml # .github/workflows/demo-publish.yml # .github/workflows/release-please.yml # .github/workflows/release.yml # .github/workflows/ux-analysis.yml # .gitignore # .golangci.yml # .release-please-manifest.json # CHANGELOG.md # CLAUDE.md # Formula/stapler-squad.rb # Makefile # config/config.go # config/config_test.go # config/defaults_test.go # config/types.go # docs/api/features/unfinished-work.md # docs/demos/accessibility-Accessibilit-2de24-ndary-routes-are-accessible-chromium-dom.gif # docs/demos/accessibility-Accessibilit-2de24-ndary-routes-are-accessible.gif # docs/demos/accessibility-Accessibilit-8ae87-us-accessibility-violations-chromium-dom.gif # docs/demos/accessibility-Accessibilit-8ae87-us-accessibility-violations.gif # docs/demos/backlog-Backlog-Backlog-To-b1401-after-it-has-been-dismissed-chromium-dom.gif # docs/demos/backlog-Backlog-Backlog-To-b1401-after-it-has-been-dismissed.gif # docs/demos/backlog-Backlog-Backlog-To-e4158-g-it-persists-across-reload-chromium-dom.gif # docs/demos/backlog-Backlog-Backlog-To-e4158-g-it-persists-across-reload.gif # docs/demos/backlog-Backlog-Empty-Stat-1590e--button-reveals-inline-form-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-1590e--button-reveals-inline-form.gif # docs/demos/backlog-Backlog-Empty-Stat-2f4c8--button-disabled-when-empty-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-2f4c8--button-disabled-when-empty.gif # docs/demos/backlog-Backlog-Empty-Stat-82f48--Clicking-cancel-hides-form-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-82f48--Clicking-cancel-hides-form.gif # docs/demos/backlog-Backlog-Empty-Stat-96beb-ycle-diagram-and-CTA-button-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-96beb-ycle-diagram-and-CTA-button.gif # docs/demos/backlog-Backlog-Filter-Zer-700a3-lters-button-resets-filters-chromium-dom.gif # docs/demos/backlog-Backlog-Filter-Zer-700a3-lters-button-resets-filters.gif # docs/demos/backlog-Backlog-Filter-Zer-9d4df-filters-shows-empty-message-chromium-dom.gif # docs/demos/backlog-Backlog-Filter-Zer-9d4df-filters-shows-empty-message.gif # docs/demos/backlog-Backlog-Item-Creat-a24f6-t-item-via-empty-state-form-chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-a24f6-t-item-via-empty-state-form.gif # docs/demos/backlog-Backlog-Item-Creat-e6bc5--the-list-after-empty-state-chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-e6bc5--the-list-after-empty-state.gif # docs/demos/backlog-Backlog-Item-Creat-ee2ef-d-with-default-priority-P3--chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-ee2ef-d-with-default-priority-P3-.gif # docs/demos/backlog-Backlog-Page-Navig-eb97f-s-accessible-and-functional-chromium-dom.gif # docs/demos/backlog-Backlog-Page-Navig-eb97f-s-accessible-and-functional.gif # docs/demos/backlog-Backlog-Page-Navig-f014a-age-loads-and-is-accessible-chromium-dom.gif # docs/demos/backlog-Backlog-Page-Navig-f014a-age-loads-and-is-accessible.gif # docs/demos/backlog-Backlog-Status-Tra-98946-ed-to-ready-via-detail-pane-chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-98946-ed-to-ready-via-detail-pane.gif # docs/demos/backlog-Backlog-Status-Tra-a3f73-Item-button-appears-in-list-chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-a3f73-Item-button-appears-in-list.gif # docs/demos/backlog-Backlog-Status-Tra-a8d60-ture-not-yet-exposed-in-UI--chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-a8d60-ture-not-yet-exposed-in-UI-.gif # docs/demos/backlog-Backlog-Triage-e2e-000e4-bled-when-repoPath-is-empty-chromium-dom.gif # docs/demos/backlog-Backlog-Triage-e2e-000e4-bled-when-repoPath-is-empty.gif # docs/demos/backlog-sources-settings-b-1d1f3-urce-and-see-it-in-the-list.gif # docs/demos/backlog-sources-settings-b-2c08b-le-a-source-s-enabled-state-chromium-dom.gif # docs/demos/backlog-sources-settings-b-2c08b-le-a-source-s-enabled-state.gif # docs/demos/backlog-sources-settings-b-6f2de-ce-removes-it-from-the-list-chromium-dom.gif # docs/demos/backlog-sources-settings-b-6f2de-ce-removes-it-from-the-list.gif # docs/demos/browser-passthrough-browse-5468d-tate-when-cdp-not-connected-chromium-dom.gif # docs/demos/browser-passthrough-browse-5468d-tate-when-cdp-not-connected.gif # docs/demos/browser-passthrough-browse-fb705-wser-tab-when-cdp-available-chromium-dom.gif # docs/demos/browser-passthrough-browse-fb705-wser-tab-when-cdp-available.gif # docs/demos/bulk-select-bulk-select-bu-1b31d-sessions-show-paused-status-chromium-dom.gif # docs/demos/bulk-select-bulk-select-bu-1b31d-sessions-show-paused-status.gif # docs/demos/bulk-select-bulk-select-bu-468ba--sessions-removed-from-list-chromium-dom.gif # docs/demos/bulk-select-bulk-select-bu-468ba--sessions-removed-from-list.gif # docs/demos/bulk-select-bulk-select-es-e26ba-xes-hidden-and-toolbar-gone-chromium-dom.gif # docs/demos/bulk-select-bulk-select-es-e26ba-xes-hidden-and-toolbar-gone.gif # docs/demos/bulk-select-bulk-select-sh-fb302-row-3-rows-1-3-are-selected-chromium-dom.gif # docs/demos/bulk-select-bulk-select-sh-fb302-row-3-rows-1-3-are-selected.gif # docs/demos/bulk-select-bulk-select-un-f128e--in-toast-sessions-reappear-chromium-dom.gif # docs/demos/bulk-select-bulk-select-un-f128e--in-toast-sessions-reappear.gif # docs/demos/demo-Demo-Flow.gif # docs/demos/enter-detection-enter-dete-79093-hould-loadPageWithoutErrors-chromium-dom.gif # docs/demos/enter-detection-enter-dete-79093-hould-loadPageWithoutErrors.gif # docs/demos/history-search-History-Sea-95175-ory-search-UI-is-accessible-chromium-dom.gif # docs/demos/history-search-History-Sea-95175-ory-search-UI-is-accessible.gif # docs/demos/insights-insights-dashboar-36984-ingOrData-When-apiAvailable-chromium-dom.gif # docs/demos/mobile-navigation-mobile-n-efd32-sionList-When-sessionsExist-chromium-dom.gif # docs/demos/mobile-navigation-mobile-n-efd32-sionList-When-sessionsExist.gif # docs/demos/nav-navigation-nav-navigat-11cca-hen-session-param-is-in-URL-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-11cca-hen-session-param-is-in-URL.gif # docs/demos/nav-navigation-nav-navigat-399f4-s-back-from-unfinished-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-399f4-s-back-from-unfinished-page.gif # docs/demos/nav-navigation-nav-navigat-84fe4-avigates-from-sessions-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-84fe4-avigates-from-sessions-page.gif # docs/demos/nav-navigation-nav-navigat-98d3c-avigates-from-sessions-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-98d3c-avigates-from-sessions-page.gif # docs/demos/nav-navigation-nav-navigat-ed5f6-hen-session-param-is-in-URL-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-ed5f6-hen-session-param-is-in-URL.gif # docs/demos/onboarding-hook-install-on-a4529-n-the-final-onboarding-step-chromium-dom.gif # docs/demos/onboarding-hook-install-on-a4529-n-the-final-onboarding-step.gif # docs/demos/review-queue-Review-Queue--12c9a-tem-from-DOM-optimistic-UI--chromium-dom.gif # docs/demos/review-queue-Review-Queue--12c9a-tem-from-DOM-optimistic-UI-.gif # docs/demos/review-queue-Review-Queue--45ba3--present-after-page-renders-chromium-dom.gif # docs/demos/review-queue-Review-Queue--45ba3--present-after-page-renders.gif # docs/demos/review-queue-Review-Queue--bf4d4-eue-page-loads-successfully-chromium-dom.gif # docs/demos/review-queue-Review-Queue--bf4d4-eue-page-loads-successfully.gif # docs/demos/review-queue-Review-Queue--e61a4-ies-acknowledge-data-testid-chromium-dom.gif # docs/demos/review-queue-Review-Queue--e61a4-ies-acknowledge-data-testid.gif # docs/demos/review-queue-Session-Creat-7ee30-eation-wizard-has-all-steps-chromium-dom.gif # docs/demos/review-queue-Session-Creat-7ee30-eation-wizard-has-all-steps.gif # docs/demos/review-queue-Session-Creat-8345c--form-has-required-test-IDs-chromium-dom.gif # docs/demos/review-queue-Session-Creat-8345c--form-has-required-test-IDs.gif # docs/demos/rules-yaml-import-rules-ya-19648--state-has-explanatory-text-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-19648--state-has-explanatory-text.gif # docs/demos/rules-yaml-import-rules-ya-3b89e-rt-duplicate-overwrite-mode-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-3b89e-rt-duplicate-overwrite-mode.gif # docs/demos/rules-yaml-import-rules-ya-552d0-les-and-shows-preview-cards-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-552d0-les-and-shows-preview-cards.gif # docs/demos/rules-yaml-import-rules-ya-62f98-ws-inline-validation-errors-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-62f98-ws-inline-validation-errors.gif # docs/demos/rules-yaml-import-rules-ya-6a69d-d-rules-and-refreshes-table-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-6a69d-d-rules-and-refreshes-table.gif # docs/demos/rules-yaml-import-rules-ya-7d21c--yaml-button-downloads-file-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-7d21c--yaml-button-downloads-file.gif # docs/demos/rules-yaml-import-rules-ya-daed9--import-duplicate-skip-mode-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-daed9--import-duplicate-skip-mode.gif # docs/demos/rules-yaml-import-rules-ya-fcaa2-port-modal-opens-and-closes-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-fcaa2-port-modal-opens-and-closes.gif # docs/demos/session-create-directory-d-0e2cd-ory-session-type-in-payload-chromium-dom.gif # docs/demos/session-create-directory-d-0e2cd-ory-session-type-in-payload.gif # docs/demos/session-create-directory-d-11fc0--is-disabled-without-a-path-chromium-dom.gif # docs/demos/session-create-directory-d-11fc0--is-disabled-without-a-path.gif # docs/demos/session-create-directory-d-45cc2-ry-field-for-directory-mode-chromium-dom.gif # docs/demos/session-create-directory-d-45cc2-ry-field-for-directory-mode.gif # docs/demos/session-create-directory-d-7ec73-irectory-type-is-selectable-chromium-dom.gif # docs/demos/session-create-directory-d-7ec73-irectory-type-is-selectable.gif # docs/demos/session-create-directory-d-a9bd3--when-directory-is-selected-chromium-dom.gif # docs/demos/session-create-directory-d-a9bd3--when-directory-is-selected.gif # docs/demos/session-create-existing-wo-3a5aa-when-worktree-path-is-empty-chromium-dom.gif # docs/demos/session-create-existing-wo-3a5aa-when-worktree-path-is-empty.gif # docs/demos/session-create-existing-wo-6a4ba--for-existing-worktree-mode-chromium-dom.gif # docs/demos/session-create-existing-wo-6a4ba--for-existing-worktree-mode.gif # docs/demos/session-create-existing-wo-7131c-rktree-option-is-selectable-chromium-dom.gif # docs/demos/session-create-existing-wo-7131c-rktree-option-is-selectable.gif # docs/demos/session-create-existing-wo-79c0c-th-worktree-path-in-payload-chromium-dom.gif # docs/demos/session-create-existing-wo-79c0c-th-worktree-path-in-payload.gif # docs/demos/session-create-existing-wo-cde14-isting-worktree-is-selected-chromium-dom.gif # docs/demos/session-create-existing-wo-cde14-isting-worktree-is-selected.gif # docs/demos/session-create-existing-wo-dbe1a-isting-worktree-is-selected-chromium-dom.gif # docs/demos/session-create-existing-wo-dbe1a-isting-worktree-is-selected.gif # docs/demos/session-create-new-project-057be-irectory-hides-branch-field-chromium-dom.gif # docs/demos/session-create-new-project-057be-irectory-hides-branch-field.gif # docs/demos/session-create-new-project-1a3d0-parent-dir-and-project-name-chromium-dom.gif # docs/demos/session-create-new-project-1a3d0-parent-dir-and-project-name.gif # docs/demos/session-create-new-project-651a4-up-defaults-to-New-Worktree-chromium-dom.gif # docs/demos/session-create-new-project-651a4-up-defaults-to-New-Worktree.gif # docs/demos/session-create-new-project-789fd-and-project-name-are-filled-chromium-dom.gif # docs/demos/session-create-new-project-789fd-and-project-name-are-filled.gif # docs/demos/session-create-new-project-8f754-m-sends-correct-RPC-payload-chromium-dom.gif # docs/demos/session-create-new-project-8f754-m-sends-correct-RPC-payload.gif # docs/demos/session-create-new-project-90413-rectory-path-does-not-exist-chromium-dom.gif # docs/demos/session-create-new-project-90413-rectory-path-does-not-exist.gif # docs/demos/session-create-new-project-c4ade-it-without-creating-session-chromium-dom.gif # docs/demos/session-create-new-project-c4ade-it-without-creating-session.gif # docs/demos/session-create-new-project-d589d-s-visible-in-creation-panel-chromium-dom.gif # docs/demos/session-create-new-project-d589d-s-visible-in-creation-panel.gif # docs/demos/session-create-new-project-e22ba-hen-New-Project-is-selected-chromium-dom.gif # docs/demos/session-create-new-project-e22ba-hen-New-Project-is-selected.gif # docs/demos/session-create-new-project-ef382-dir-and-project-name-fields-chromium-dom.gif # docs/demos/session-create-new-project-ef382-dir-and-project-name-fields.gif # docs/demos/session-create-new-project-fb84d-t-with-createIfMissing-true-chromium-dom.gif # docs/demos/session-create-new-project-fb84d-t-with-createIfMissing-true.gif # docs/demos/session-create-new-worktre-0fc57-ee-is-the-default-selection-chromium-dom.gif # docs/demos/session-create-new-worktre-0fc57-ee-is-the-default-selection.gif # docs/demos/session-create-new-worktre-7f600-type-with-branch-in-payload-chromium-dom.gif # docs/demos/session-create-new-worktre-7f600-type-with-branch-in-payload.gif # docs/demos/session-create-new-worktre-9dd2f-ckbox-for-new-worktree-mode-chromium-dom.gif # docs/demos/session-create-new-worktre-9dd2f-ckbox-for-new-worktree-mode.gif # docs/demos/session-create-new-worktre-ce636-itle-as-branch-is-unchecked-chromium-dom.gif # docs/demos/session-create-new-worktre-ce636-itle-as-branch-is-unchecked.gif # docs/demos/session-create-new-worktre-ce772--title-as-branch-is-checked-chromium-dom.gif # docs/demos/session-create-new-worktre-ce772--title-as-branch-is-checked.gif # docs/demos/session-lifecycle-Session--10237-Session-status-filter-works-chromium-dom.gif # docs/demos/session-lifecycle-Session--10237-Session-status-filter-works.gif # docs/demos/session-lifecycle-Session--2a57f-ion-create-UI-is-accessible-chromium-dom.gif # docs/demos/session-lifecycle-Session--2a57f-ion-create-UI-is-accessible.gif # docs/demos/session-lifecycle-Session--7b493-ssion-management-page-loads-chromium-dom.gif # docs/demos/session-lifecycle-Session--7b493-ssion-management-page-loads.gif # docs/demos/session-lifecycle-Session--995da-paused-sessions-are-visible-chromium-dom.gif # docs/demos/session-lifecycle-Session--995da-paused-sessions-are-visible.gif # docs/demos/shell-tabs-shell-tabs-shel-39ca1-ellDialog-When-ctrlTPressed-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-39ca1-ellDialog-When-ctrlTPressed.gif # docs/demos/shell-tabs-shell-tabs-shel-684a1-lTab-When-plusButtonClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-684a1-lTab-When-plusButtonClicked.gif # docs/demos/shell-tabs-shell-tabs-shel-70877-actionMenuSpawnShellClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-70877-actionMenuSpawnShellClicked.gif # docs/demos/shell-tabs-shell-tabs-shel-e3a96-ab-When-deleteButtonClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-e3a96-ab-When-deleteButtonClicked.gif # docs/demos/smoke-Smoke-Tests-home-page-loads-successfully-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-home-page-loads-successfully.gif # docs/demos/smoke-Smoke-Tests-navigation-header-is-present-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-navigation-header-is-present.gif # docs/demos/smoke-Smoke-Tests-review-queue-page-loads-successfully-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-review-queue-page-loads-successfully.gif # docs/demos/terminal-mobile-overflow-m-50056--visible-on-mobile-viewport-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-50056--visible-on-mobile-viewport.gif # docs/demos/terminal-mobile-overflow-m-53f8a-le-without-opening-overflow-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-53f8a-le-without-opening-overflow.gif # docs/demos/terminal-mobile-overflow-m-64891--row-with-secondary-buttons-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-64891--row-with-secondary-buttons.gif # docs/demos/terminal-mobile-overflow-m-76599-Less-hides-the-overflow-row-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-76599-Less-hides-the-overflow-row.gif # docs/demos/terminal-resize-terminal-r-9b065-tays-connected-after-resize.gif # docs/demos/terminal-resize-terminal-r-e5784--after-resize-dom-renderer--chromium-dom.gif # docs/demos/theme-background-theme-bac-3d682-background-under-dark-theme-chromium-dom.gif # docs/demos/theme-background-theme-bac-3d682-background-under-dark-theme.gif # docs/demos/touch-targets-Touch-target-0c83a--keyboard-keys-are-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-0c83a--keyboard-keys-are-≥44×44px.gif # docs/demos/touch-targets-Touch-target-31878-tton-is-≥44×44px-on-desktop-chromium-dom.gif # docs/demos/touch-targets-Touch-target-31878-tton-is-≥44×44px-on-desktop.gif # docs/demos/touch-targets-Touch-target-66543--session-button-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-66543--session-button-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-b4371--actions-button-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-b4371--actions-button-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-cbd53--toolbar-toggle-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-cbd53--toolbar-toggle-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-e8172-om-nav-items-are-≥44px-tall-chromium-dom.gif # docs/demos/touch-targets-Touch-target-e8172-om-nav-items-are-≥44px-tall.gif # docs/demos/visual-regression-omnibar-open-chromium-dom.gif # docs/demos/visual-regression-omnibar-open.gif # docs/demos/visual-regression-session-list-empty-state-chromium-dom.gif # docs/demos/visual-regression-session-list-empty-state.gif # docs/demos/workspace-management-Works-debfe-e-information-is-accessible-chromium-dom.gif # docs/demos/workspace-management-Works-debfe-e-information-is-accessible.gif # docs/demos/workspace-management-Works-ee243-h---Review-queue-page-loads-chromium-dom.gif # docs/demos/workspace-management-Works-ee243-h---Review-queue-page-loads.gif # docs/registry/features/backend/ImportGitHubIssue.json # docs/registry/features/backend/ListGitHubIssues.json # docs/registry/features/backend/SearchGitHubRepos.json # docs/registry/features/backend/backlog/approve-plan.json # docs/registry/features/backend/backlog/archive-item.json # docs/registry/features/backend/backlog/attach-session.json # docs/registry/features/backend/backlog/cancel-triage.json # docs/registry/features/backend/backlog/create-item.json # docs/registry/features/backend/backlog/create-source.json # docs/registry/features/backend/backlog/delete-item.json # docs/registry/features/backend/backlog/delete-source.json # docs/registry/features/backend/backlog/get-item.json # docs/registry/features/backend/backlog/get-sync-history.json # docs/registry/features/backend/backlog/list-items.json # docs/registry/features/backend/backlog/list-sources.json # docs/registry/features/backend/backlog/override-verdict.json # docs/registry/features/backend/backlog/spawn-session.json # docs/registry/features/backend/backlog/suggest-next.json # docs/registry/features/backend/backlog/transition-status.json # docs/registry/features/backend/backlog/trigger-re-review.json # docs/registry/features/backend/backlog/trigger-sync.json # docs/registry/features/backend/backlog/trigger-triage.json # docs/registry/features/backend/backlog/update-item.json # docs/registry/features/backend/backlog/update-source.json # docs/registry/features/backend/session/run-one-shot.json # docs/registry/features/frontend/ui/backlog-board.json # docs/registry/features/frontend/ui/backlog-item-card.json # docs/registry/features/frontend/ui/backlog-item-detail.json # docs/registry/features/frontend/ui/backlog-item-form.json # docs/registry/features/frontend/ui/backlog-list-page.json # docs/tasks/completed/system-service-autostart.md # gen/proto/go/session/v1/backlog.pb.go # gen/proto/go/session/v1/session.pb.go # gen/proto/go/session/v1/sessionv1connect/backlog.connect.go # gen/proto/go/session/v1/types.pb.go # github/client.go # github/clone.go # github/etag_cache.go # github/http_client.go # github/keychain.go # github/repos.go # github/user_pr_cache.go # go.mod # go.sum # main.go # pkg/events/types.go # profiling/profiling.go # proto/session/v1/backlog.proto # proto/session/v1/session.proto # proto/session/v1/types.proto # scripts/build-tmux.sh # scripts/install-service.sh # scripts/setup-codesign.sh # server/adapters/instance_adapter.go # server/adapters/review_queue_adapter.go # server/adapters/review_queue_adapter_test.go # server/analytics/subscriber.go # server/analytics/subscriber_test.go # server/dependencies.go # server/dependencies_test.go # server/events/forward.go # server/features/backlog.go # server/mcp/server.go # server/mcp/server_integration_test.go # server/mcp/tools_backlog.go # server/mcp/tools_backlog_test.go # server/mcp/tools_github.go # server/mcp/tools_goal.go # server/mcp/tools_goal_test.go # server/mcp/tools_terminal.go # server/mcp/tools_terminal_test.go # server/push/subscriber.go # server/push/subscriber_test.go # server/review_queue_manager.go # server/review_queue_manager_test.go # server/server.go # server/server_integration_test.go # server/services/approval_handler.go # server/services/autonomous_orchestration_service.go # server/services/autonomous_orchestration_service_test.go # server/services/backlog_github_rpc_test.go # server/services/backlog_service.go # server/services/backlog_service_test.go # server/services/backlog_triage_harness_test.go # server/services/connectrpc_websocket.go # server/services/defaults_service.go # server/services/defaults_service_test.go # server/services/event_converter_test.go # server/services/feature_flag_service.go # server/services/file_service.go # server/services/file_service_test.go # server/services/github_user_service.go # server/services/hook_injector.go # server/services/hook_injector_test.go # server/services/hook_receivers.go # server/services/local_file_service.go # server/services/local_file_service_test.go # server/services/mcp_injector.go # server/services/mcp_injector_test.go # server/services/oneshot_test.go # server/services/path_completion_service.go # server/services/path_completion_service_test.go # server/services/push_service.go # server/services/search_service.go # server/services/session_service.go # server/services/session_service_create_test.go # server/services/session_service_shells.go # server/services/session_service_stream_terminal_test.go # server/services/session_service_test.go # server/services/terminal_service.go # server/services/unfinished_work_service.go # server/services/unfinished_work_test.go # server/services/workspace_service_test.go # session/actor.go # session/autonomous_driver.go # session/autonomous_driver_test.go # session/backlog.go # session/backlog_commands.go # session/backlog_commands_test.go # session/backlog_context.go # session/backlog_context_test.go # session/backlog_integration_test.go # session/backlog_lifecycle.go # session/backlog_lifecycle_test.go # session/backlog_plugin_github.go # session/backlog_plugin_github_prs.go # session/backlog_plugin_github_test.go # session/backlog_review.go # session/backlog_review_test.go # session/backlog_sync.go # session/backlog_sync_test.go # session/backlog_test.go # session/backlog_triage.go # session/backlog_triage_test.go # session/capture_test.go # session/circular_buffer.go # session/claude_command_builder.go # session/claude_controller.go # session/detection/approval.go # session/detection/detector_test.go # session/detection/pattern_set.go # session/detection/pattern_set_test.go # session/detection/proto_mapping.go # session/ent/backlogitem.go # session/ent/backlogitem/backlogitem.go # session/ent/backlogitem/where.go # session/ent/backlogitem_create.go # session/ent/backlogitem_query.go # session/ent/backlogitem_update.go # session/ent/backlogstatusevent.go # session/ent/backlogstatusevent/backlogstatusevent.go # session/ent/backlogstatusevent/where.go # session/ent/backlogstatusevent_create.go # session/ent/backlogstatusevent_update.go # session/ent/client.go # session/ent/ent.go # session/ent/hook/hook.go # session/ent/itemsession.go # session/ent/itemsession/itemsession.go # session/ent/itemsession/where.go # session/ent/itemsession_create.go # session/ent/itemsession_update.go # session/ent/migrate/schema.go # session/ent/mutation.go # session/ent/predicate/predicate.go # session/ent/runtime.go # session/ent/schema/backlog_item.go # session/ent/schema/backlog_status_event.go # session/ent/schema/item_session.go # session/ent/schema/session_goal.go # session/ent/sessiongoal.go # session/ent/sessiongoal/sessiongoal.go # session/ent/sessiongoal/where.go # session/ent/sessiongoal_create.go # session/ent/sessiongoal_update.go # session/ent/tx.go # session/ent_repository.go # session/ent_repository_backlog.go # session/ent_repository_backlog_test.go # session/external_tmux_streamer.go # session/git/util.go # session/git/worktree.go # session/git/worktree_creation_test.go # session/git/worktree_git.go # session/git/worktree_git_test.go # session/git/worktree_ops.go # session/git_worktree_manager.go # session/headless/caller.go # session/headless/client.go # session/headless/fake_runner.go # session/headless/features.go # session/headless/features_test.go # session/headless/integration_test.go # session/headless/pool_test.go # session/headless/runner.go # session/health.go # session/health_test.go # session/hibernation_sweeper_test.go # session/instance.go # session/instance_actor_setters.go # session/instance_approval.go # session/instance_checkpoint.go # session/instance_claude.go # session/instance_controller.go # session/instance_hibernate.go # session/instance_lifecycle_test.go # session/instance_serialization.go # session/instance_shells.go # session/instance_state.go # session/instance_terminal.go # session/instance_tmux.go # session/instance_tmux_test.go # session/instance_workspace.go # session/instance_workspace_test.go # session/instance_worktree.go # session/integration_test.go # session/memory/reader.go # session/mux/multiplexer.go # session/mux/testmain_test.go # session/orphan_sweep.go # session/orphan_sweep_test.go # session/pr_status_poller.go # session/pty_discovery.go # session/pty_discovery_test.go # session/repo_path.go # session/repository.go # session/review_queue_determiner.go # session/review_queue_determiner_test.go # session/review_queue_poller.go # session/review_queue_poller_test.go # session/review_state.go # session/session_driver.go # session/session_driver_test.go # session/startup_scanner_test.go # session/state_machine_test.go # session/status_mapping.go # session/status_mapping_test.go # session/storage.go # session/storage_backlog.go # session/storage_goal_test.go # session/storage_test.go # session/tmux/control_mode.go # session/tmux/server_registry.go # session/tmux/server_registry_integration_test.go # session/tmux/testmain_test.go # session/tmux/tmux.go # session/tmux/tmux_test.go # session/tmux_backend_test.go # session/tmux_process_manager.go # session/unfinished/gogit_vcs_reader.go # session/unfinished/gogit_vcs_reader_limits_test.go # session/unfinished/scanner.go # session/unfinished/scanner_test.go # session/worktree_pr_poller.go # telemetry/telemetry.go # tests/e2e/accessibility.spec.ts # tests/e2e/backlog-sources-settings.spec.ts # tests/e2e/backlog.spec.ts # tests/e2e/fixtures/clean-theme.json # tests/e2e/fixtures/cyberpunk77-theme.json # tests/e2e/fixtures/matrix-theme.json # tests/e2e/fixtures/wh40k-theme.json # tests/e2e/nav-navigation.spec.ts # tests/e2e/package-lock.json # tests/e2e/pages/BacklogPage.ts # tests/e2e/session-create-directory.spec.ts # tests/e2e/session-create-existing-worktree.spec.ts # tests/e2e/touch-targets.spec.ts # tests/e2e/unfinished-work.spec.ts # third_party/tmux~origin_main # tools/lint/cmd/linter/main.go # tools/scanner/backend/proto_scanner.go # web-app/.eslintrc.json # web-app/jest.config.js # web-app/jest.setup.js # web-app/lighthouserc.json # web-app/package-lock.json # web-app/package.json # web-app/pnpm-lock.yaml # web-app/src/__mocks__/styleMock.js # web-app/src/app/backlog/backlog.css.ts # web-app/src/app/backlog/board/page.tsx # web-app/src/app/backlog/page.tsx # web-app/src/app/config/ConfigPageContent.tsx # web-app/src/app/config/config.css.ts # web-app/src/app/files/page.tsx # web-app/src/app/insights/InsightsDashboard.tsx # web-app/src/app/insights/SessionDetailDrawer.css.ts # web-app/src/app/insights/SessionDetailDrawer.tsx # web-app/src/app/insights/SessionsTable.css.ts # web-app/src/app/insights/SessionsTable.tsx # web-app/src/app/page.tsx # web-app/src/app/rules/page.css.ts # web-app/src/app/rules/page.tsx # web-app/src/app/settings/backlog-sources/page.tsx # web-app/src/app/settings/features/page.tsx # web-app/src/app/settings/page.tsx # web-app/src/app/settings/settings.css.ts # web-app/src/app/unfinished/UnfinishedTab.css.ts # web-app/src/app/unfinished/UnfinishedTab.tsx # web-app/src/app/unfinished/page.tsx # web-app/src/components/backlog/BacklogBoard.css.ts # web-app/src/components/backlog/BacklogBoard.tsx # web-app/src/components/backlog/BacklogItemBadge.tsx # web-app/src/components/backlog/BacklogItemCard.css.ts # web-app/src/components/backlog/BacklogItemCard.tsx # web-app/src/components/backlog/BacklogItemDetail.css.ts # web-app/src/components/backlog/BacklogItemDetail.regression.test.tsx # web-app/src/components/backlog/BacklogItemDetail.tsx # web-app/src/components/backlog/BacklogItemForm.css.ts # web-app/src/components/backlog/BacklogItemForm.test.tsx # web-app/src/components/backlog/BacklogItemForm.tsx # web-app/src/components/backlog/BacklogItemPanel.css.ts # web-app/src/components/backlog/BacklogItemPanel.tsx # web-app/src/components/backlog/GateVerdictBox.css.ts # web-app/src/components/backlog/GateVerdictBox.test.tsx # web-app/src/components/backlog/GateVerdictBox.tsx # web-app/src/components/backlog/GitHubIssuePicker.css.ts # web-app/src/components/backlog/GitHubIssuePicker.tsx # web-app/src/components/backlog/SessionMonitor.css.ts # web-app/src/components/backlog/SessionMonitor.tsx # web-app/src/components/backlog/TriageReviewPanel.css.ts # web-app/src/components/backlog/TriageReviewPanel.test.tsx # web-app/src/components/backlog/TriageReviewPanel.tsx # web-app/src/components/files/LocalFileBrowser.css.ts # web-app/src/components/files/LocalFileBrowser.tsx # web-app/src/components/layout/DrawerNav.tsx # web-app/src/components/layout/Header.css.ts # web-app/src/components/layout/Header.tsx # web-app/src/components/layout/__tests__/DrawerNav.test.tsx # web-app/src/components/layout/__tests__/Header.test.tsx # web-app/src/components/pane/PaneHeader.tsx # web-app/src/components/pane/PaneSplitRenderer.tsx # web-app/src/components/rules/RulePreview.tsx # web-app/src/components/sessions/ApprovalAnalyticsPanel.css.ts # web-app/src/components/sessions/DetectionEventsPanel.tsx # web-app/src/components/sessions/FileContentViewer.css.ts # web-app/src/components/sessions/FileContentViewer.tsx # web-app/src/components/sessions/FileTree.css.ts # web-app/src/components/sessions/FileTree.tsx # web-app/src/components/sessions/FilesTab.css.ts # web-app/src/components/sessions/FilesTab.tsx # web-app/src/components/sessions/NewShellDialog.css.ts # web-app/src/components/sessions/NewShellDialog.tsx # web-app/src/components/sessions/Omnibar.tsx # web-app/src/components/sessions/OmnibarCreationPanel.tsx # web-app/src/components/sessions/QuickOpenPalette.css.ts # web-app/src/components/sessions/QuickOpenPalette.tsx # web-app/src/components/sessions/RecentFilesSection.css.ts # web-app/src/components/sessions/RecentFilesSection.tsx # web-app/src/components/sessions/ReviewQueuePanel.css.ts # web-app/src/components/sessions/ReviewQueuePanel.tsx # web-app/src/components/sessions/SessionActionsOverflow.tsx # web-app/src/components/sessions/SessionCard.tsx # web-app/src/components/sessions/SessionDetailView.tsx # web-app/src/components/sessions/SessionList.css.ts # web-app/src/components/sessions/SessionList.tsx # web-app/src/components/sessions/SessionRow.tsx # web-app/src/components/sessions/StatusBadge.tsx # web-app/src/components/sessions/SubStatusChip.tsx # web-app/src/components/sessions/TerminalOutput.tsx # web-app/src/components/sessions/VcsPanel.css.ts # web-app/src/components/sessions/VcsPanel.tsx # web-app/src/components/sessions/XtermTerminal.tsx # web-app/src/components/sessions/__tests__/FileTree.test.tsx # web-app/src/components/sessions/__tests__/NewShellDialog.test.tsx # web-app/src/components/sessions/__tests__/Omnibar.alias.test.tsx # web-app/src/components/sessions/__tests__/Omnibar.pathcompletion.test.tsx # web-app/src/components/sessions/__tests__/OmnibarCreationPanel.attach.test.tsx # web-app/src/components/sessions/__tests__/ReviewQueuePanel.test.tsx # web-app/src/components/sessions/__tests__/SessionActionsOverflow.test.tsx # web-app/src/components/sessions/__tests__/SessionCard.approval-suppression.test.tsx # web-app/src/components/sessions/__tests__/SessionCard.click.test.tsx # web-app/src/components/sessions/__tests__/SessionDetail.embedded.test.tsx # web-app/src/components/sessions/__tests__/StatusBadge.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.enter-detection.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.logstream.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.reconnect.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.toolbar-analytics.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.upload.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutputBug.test.tsx # web-app/src/components/sessions/__tests__/XtermTerminal.test.tsx # web-app/src/components/settings/GlobalDefaultsForm.css.ts # web-app/src/components/settings/GlobalDefaultsForm.tsx # web-app/src/components/shared/DiffRenderer.css.ts # web-app/src/components/shared/DiffRenderer.tsx # web-app/src/components/ui/NotificationPanel.css.ts # web-app/src/components/ui/RepoPathInput.tsx # web-app/src/components/unfinished/CommitPushModal.css.ts # web-app/src/components/unfinished/CommitPushModal.tsx # web-app/src/components/unfinished/UnfinishedItem.css.ts # web-app/src/components/unfinished/UnfinishedItemDetail.css.ts # web-app/src/components/unfinished/UnfinishedItemDetail.tsx # web-app/src/components/unfinished/WorktreeDiffModal.css.ts # web-app/src/components/workflows/WorkflowForm.tsx # web-app/src/gen/session/v1/backlog_pb.ts # web-app/src/gen/session/v1/session_pb.ts # web-app/src/gen/session/v1/types_pb.ts # web-app/src/lib/backlog/status.ts # web-app/src/lib/contexts/FeatureFlagsContext.tsx # web-app/src/lib/contexts/NotificationContext.tsx # web-app/src/lib/contexts/OmnibarContext.tsx # web-app/src/lib/contexts/SessionServiceContext.tsx # web-app/src/lib/features/features/unfinished-work.ts # web-app/src/lib/hooks/__tests__/useBrowserLogStream.test.ts # web-app/src/lib/hooks/__tests__/useSessionNotifications.test.ts # web-app/src/lib/hooks/__tests__/useSessionService.test.ts # web-app/src/lib/hooks/__tests__/useTerminalFlowControl.test.ts # web-app/src/lib/hooks/useBacklogService.ts # web-app/src/lib/hooks/useBrowserLogStream.ts # web-app/src/lib/hooks/useGitHubIssuePicker.ts # web-app/src/lib/hooks/useSessionNotifications.ts # web-app/src/lib/hooks/useSessionService.ts # web-app/src/lib/hooks/useTerminalFlowControl.ts # web-app/src/lib/hooks/useTerminalStream.ts # web-app/src/lib/hooks/useVcsStatus.ts # web-app/src/lib/hooks/useWorktreeSuggestions.ts # web-app/src/lib/nav-pages.ts # web-app/src/lib/omnibar/actions/dispatch.test.ts # web-app/src/lib/omnibar/actions/dispatch.ts # web-app/src/lib/omnibar/actions/types.ts # web-app/src/lib/omnibar/detector.test.ts # web-app/src/lib/omnibar/detectors/CommandDetector.ts # web-app/src/lib/omnibar/types.ts # web-app/src/lib/routes.ts # web-app/src/lib/store/__tests__/reviewQueueSlice.test.ts # web-app/src/lib/store/__tests__/sessionsSlice.test.ts # web-app/src/lib/store/store.ts # web-app/src/lib/utils/__tests__/deriveWorkingState.test.ts # web-app/src/lib/utils/deriveWorkingState.ts # web-app/src/lib/utils/issuePickerCache.ts # web-app/src/lib/utils/parseDiff.ts # web-app/src/styles/pane/paneHeader.css.ts # web-app/src/styles/theme-contract.css.ts # web-app/src/styles/theme.css.ts # web-app/tests/e2e/navigation.spec.ts
…nflict resolution
Resolving the origin/main merge (LFS history cutover left this branch far
behind main's history) required bulk-resolving hundreds of untouched-by-this-PR
conflicts to main's side. That correctly dropped stale duplicate content, but
also silently dropped a handful of pre-existing fixes/features that exist only
on this branch (inherited from an earlier, now-unreachable main state) with no
main-side equivalent:
- session/detection/proto_mapping.go: restore DetectedStatusToSubStatus,
which proto_mapping_test.go (auto-merged, unaffected by the bulk resolve)
already exercises.
- web-app/src/components/sessions/SubStatusChip.tsx: restore the
forward-compatible "render nothing" default case for unrecognized
SubStatus values instead of the throwing assertNever main's revision uses
— proto enums are forward-compatible and one unrecognized wire value must
not crash the sessions UI.
- TerminalOutput{,.reconnect,.enter-detection,.logstream,.toolbar-analytics,
.upload}.test.tsx and TerminalOutputBug.test.tsx: add the
requestFullResync/markResyncComplete/markPaneResponseReceived mock fields
useVisibilityResync (only present on this branch, not main) calls
unconditionally on unmount/session-id change, plus the XtermTerminal
mock's resize() stub the pre-sizing effect now calls.
Found by running the full frontend/backend test suites after the merge
rather than trusting the conflict resolution alone.
Contributor
✅ Registry ValidationTest Coverage: 19/179 features have
|
Contributor
Go Benchmarks (Tier 1) |
Contributor
E2E RPC Latency |
Contributor
UX Analysis
|
Contributor
📊 Feature E2E CoverageFeature coverage report unavailable
|
…e peer dep @xterm/xterm was bumped to 6.0.0 in an unrelated PR after PR #272 added @xterm/addon-canvas for the WebGL->Canvas fallback (ADR-001). Upstream removed the canvas renderer from the xterm.js monorepo entirely as of 6.0.0 (xtermjs/xterm.js#5105), so addon-canvas@0.7.0's peer dependency (^5.0.0) will never be updated -- pnpm install only warns, it doesn't fail, so CI stayed green with no signal either way. Verified ground truth instead of trusting the peer-dep warning: a real, unmocked CanvasAddon activates, resizes, writes, and disposes cleanly against a real, unmocked xterm 6 Terminal (jsdom has no real 2D canvas context, so a minimal fake one stands in, same workaround already used for @xterm/addon-serialize). Added a regression test proving this and documented the finding in ADR-001's addendum and inline in XtermTerminal.tsx, so a future xterm major bump gets caught instead of silently shipping a dead fallback behind a green peer-dep warning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
Contributor
🎬 E2E Feature Demos2 shard(s) recorded feature flows for this PR. recordings shard 1 Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days. |
Contributor
✅ Registry ValidationTest Coverage: 19/179 features have
|
Contributor
Frontend Terminal Throughput |
tstapler
marked this pull request as ready for review
July 28, 2026 01:32
tstapler
added a commit
that referenced
this pull request
Jul 28, 2026
…#272's merge commit 591059a already removed these from git and gitignored benchmarks/ (baselines are persisted via GitHub Actions cache instead) — but PR #272's branch predated that fix and still had them tracked, so merging it resurrected all four as tracked files again. .gitignore doesn't stop a merge from reintroducing already-tracked-on-one-side content, so this needs its own removal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
This was referenced Jul 29, 2026
tstapler
added a commit
that referenced
this pull request
Aug 3, 2026
Fixes 5 confirmed MAJOR findings from the Gate 2 review pass:
1. TestRequestReview_ReportsDistinctMessage_WhenCASPreconditionFails
called require.NoError from inside a spawned goroutine — t.FailNow()
only unwinds that goroutine, not the test goroutine. Both racers now
send {result, err} through the channel and every require/assert call
happens in the main test goroutine. The same channel type is reused
for the new report_duplicate CAS test (fix 3).
2. Added TestReportDuplicate_VerifyGitHubRefExists_DispatchesPRTypeToRealGetPR,
the first test in the package that leaves verifyGitHubRef nil so the
real verifyGitHubRefExists dispatch switch (PR/Issue/Commit -> GetPR/
GetIssue/GetCommit) actually runs, pointed at an httptest.Server via
githubpkg.GhBaseURL (mirrors github/repos_pr_test.go's pattern).
3. Added TestReportDuplicate_ReportsDistinctMessage_WhenCASPreconditionFails,
mirroring the request_review CAS regression test for report_duplicate's
identical errors.Is(transErr, session.ErrPreconditionFailed) branch.
4. Added TestReportDuplicate_DoesNotTreatPrefixRefAsIdempotentMatch,
proving a shorter ref that is a literal string-prefix of an
already-recorded longer ref (.../pull/27 vs .../pull/272) is not
misclassified as the idempotent no-op retry.
5. requestReview's verification-notes persistence overwrote
VerificationNotes via UpdateItemSessionVerificationNotes, silently
erasing prior evidence (e.g. from an earlier report_duplicate call)
on the same ItemSession. reportDuplicate already had this fixed with
an append pattern; applied the same fix to requestReview and added
TestRequestReview_AppendsToExistingVerificationNotes_RatherThanOverwriting
as a new test, leaving the existing
TestRequestReview_PersistsVerificationNotesOnWorkSession assertions
unmodified per AC9/FR9.
All 5 pre-existing TestRequestReview_* tests still pass with their
original assertions intact. go build/vet/gofmt/golangci-lint clean on
server/mcp and github packages; go test ./server/mcp/... ./github/...
-race all green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxSH3xUu3fyFnprsybnRiN
tstapler
added a commit
that referenced
this pull request
Aug 3, 2026
…308) * chore(sdd): DX research pass for jest-ci-wiring No end-user-facing UX applies (pure CI/infra change) — lightweight DX pass instead, comparing failure surfacing to golangci-lint annotations vs plain-log precedent (ESLint/gofmt) and flagging --ci flag as a zero-cost quick win. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): stack research for backlog-status-transitions Documents the BacklogStatus enum pattern, mark3labs/mcp-go tool registration/handler shape, and confirms the ent status column needs no migration for a new terminal value. * chore(sdd): architecture research for flaky-hook-url-tests * chore(sdd): build-vs-buy research for detector-plugins Evaluates TOML parsing (go-toml v2), regex engine (stdlib RE2), and hot-reload (fsnotify) choices, plus config-aggregation frameworks (Viper/koanf) and existing plugin-system prior art (Caddy/Vector/Grafana), for the user-extensible agent detector plugin loader. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): pitfalls research for detector-plugins Covers RE2/ReDoS guarantees and limits, fsnotify hot-reload gotchas (temp-file-rename, debounce, inotify limits, macOS/Linux differences), TOML/config pitfalls, the atomic-swap concurrency pattern to reuse from worktree_git.go, and fail2ban/ESLint/VS Code prior art. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): UX research for token-cost-tracking Documents existing table/sort patterns to mirror (SessionDetailDrawer's Tools Breakdown, backlog page's aria-sort), the proto gaps blocking AC-1 (per-turn) and AC-6 (cache split), and concrete labeling/empty-state/ sort-order decisions for AC-1 through AC-3. * chore(sdd): architecture research for token-cost-tracking * chore(sdd): pitfalls research for token-cost-tracking gap closure Research pass on what commonly breaks when adding to an already-live analytics feature: the #280 silent-$0.00 precedent, TokenStore RWMutex contention, jumping-list risk for async-sorted cost data, stale registry schema.json vs real frontend entry shape, and the backlogItemEventSender pattern needed before WatchInsights is testable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): feature/edge-case research for token-cost-tracking Documents edge cases and unstated scope for the 6 gap-closing ACs: AC-1 requires a proto change (TurnTimeline isn't exposed over the wire, not just unwired in the frontend); AC-2's SessionList.tsx has zero token data wired in today (TokenBadge is unused dead code, contradicting requirements.md); AC-3 needs no backend change (ListSessionTokens sort_by already implemented, SessionsTable.tsx's sort is just hardcoded); AC-4 needs the same narrow-interface refactor WatchBacklogItems already established for testing connect-go streaming RPCs; AC-5's 5 target files share one +feature marker with an existing aggregated registry entry, creating a collision risk; AC-6 is free (client-side formula, data already on the wire). * chore(sdd): implementation plan + ADR for flaky-hook-url-tests Phase 3 planning artifacts: creative pass, domain glossary, pattern decisions, task breakdown (3 epics / 4 stories / 9 tasks), and ADR-001 justifying -p 1 over an isolated second -race invocation for the CI contention mitigation. Also commits the earlier requirements.md and research/*.md that were still uncommitted from phases 1-2. * chore(sdd): plan + ADR for subagent-spawn-tracking Phase 3 architecture/task-breakdown plan (7 verified pipeline hops from regex capture through proto to the SubStatusChip badge) plus ADR-001 documenting the statusCacheEntry cache-coherence requirement between GetCurrentStatus and GetStatusAndIdleInfo. Also commits the requirements and research docs from earlier phases that were still untracked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): implementation plan + ADRs for detector-plugins Phase 3 planning artifacts: plan.md (3 phases, 6 epics, 14 stories, 41 tasks, 30-term glossary) plus four ADRs covering the go-toml/v2 dependency, the registry-level copy-on-write snapshot (vs. the existing unused StatusDetector YAML loader), TOML schema v1, and the RE2 trust boundary. Also picks up the architecture/features research files that were still untracked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): implementation plan for token-cost-tracking Phase 3 planning artifact for the token-cost-tracking gap-closure project: domain glossary, pattern decisions (new-RPC vs bolt-on for AC-1, interface extraction for AC-4's WatchInsights test, client-side derivation for AC-6), risk control, and a 5-phase task breakdown sequenced by risk (AC-4/AC-5 low-risk first, AC-2's new SessionList data join last). Also commits the requirements.md and research/{build-vs-buy,stack}.md artifacts from earlier phases that were left uncommitted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): test validation plan for flaky-hook-url-tests Maps each requirements.md scope item and plan.md task to concrete verification: regression checks for the two affected integration tests, a stress/flake-verification repro (-count=10 under artificial CPU contention), the Task 1.2.4 non-fatal-teardown check, the Task 2.1.1 coverage-artifact/-race-scope check, and the Task 2.1.2 averaged (>=3-run) wall-clock measurement. No user-facing surface, so UX Acceptance Tests section is N/A; no schema change, so Migration Test is N/A. * chore(sdd): UX design artifact for token-cost-tracking gap closure Wireframes, interaction flows, error/empty states, and UX acceptance criteria for the 4 user-facing surfaces in Phases 2-4 of the implementation plan (SessionsTable click-to-sort, ModelBreakdownChart cache-hit-rate label, SessionDetailDrawer per-turn table, SessionList Sort: Cost option). Flags a concrete contrast/layout risk in reusing TokenBadge.css.ts's badgeVariant.warning for outlier-turn highlighting in a table-cell context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): validate + review artifacts for subagent-spawn-tracking Adversarial/architecture review, UX design, validation plan, and pre-mortem for the subagent-spawn-tracking feature; plan.md patched to resolve 2 architecture-review CONCERNS, 1 adversarial-review safety concern, 2 cross-artifact-consistency BLOCKERs (title-attribute contradiction with ux.md, missing NaN/negative test coverage), and the pre-mortem's P1 (corrected a false "no debounce precedent" claim). Triad review (Product/UX/Engineering) verdict: READY TO BUILD. * chore(sdd): validation/review artifacts + plan patches for flaky-hook-url-tests Adds architecture review, adversarial review, and pre-mortem docs from SDD Phase 3/4, and patches plan.md to resolve the cross-artifact consistency blocker (missing runner-concurrency check task) and the pre-mortem P1 item (testSocketOnce misdiagnosis prevention). * chore(sdd): test validation plan for detector-plugins Maps all 6 requirements.md acceptance criteria plus plan.md's Phase 1-2 story-level Given/When/Then scenarios to concrete Go test names, organized as unit vs. integration, so implementation starts test-first. * chore(sdd): adoption plan for ci-hookurl-race-flake Research and planning surfaced that this backlog item duplicates the already-planned, already-reviewed project_plans/flaky-hook-url-tests/ (same root cause, same two tests, ADR-001 + full task breakdown never implemented). implementation/plan.md consolidates rather than re-derives: it adopts that plan/ADR wholesale, reproduces the full Epic/Story/Task breakdown here for self-containment, and re-verifies every file:line reference against the current tree (all accurate, no drift found). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): phase 4 validation, pre-mortem, and triad-review repair for detector-plugins Phase 4 (sdd:4-validate): validation.md test suite, pre-mortem.md failure analysis, and cross-artifact consistency check. The pre-mortem's P1 finding (DetectForProgram/the plugin registry has zero production call sites, so a loaded plugin would never change what a user's session actually shows) was independently confirmed by adversarial-review.md's own Blocker and by direct repo verification (idle.go's live detection path uses getDefaultPatterns(), not the registry this plan builds). Resolved by adding Epic 2.4 to plan.md, wiring ClaudeController.Start to resolve its detector via the new registry snapshot instead of the always-generic default. Also folds in a Hardening Addendum (compile-time budget, total-file-count cap, rebuildSnapshot context cancellation, InitPlugins re-entrancy guard, non-fatal seed-file-write) closing 5 previously-open adversarial-review Concerns, and requirements.md amendments (Target User, Success Metric, Risky Assumption sections; corrected plugin-directory and version-field wording) from a 3-round product/engineering triad review. * chore: untrack accidentally-swept file from detector-plugins commit project_plans/backlog-status-transitions/research/architecture.md belongs to an unrelated, concurrently in-progress backlog item and was swept into the previous commit because it was already staged in the shared index at commit time. Untracking (not deleting) it here so it returns to the working tree for its own session to commit under its own message. * chore(sdd): validation artifacts + review-driven patches for ci-hookurl-race-flake Phase 4 (validate) for the flaky hook-URL/MCP-URL CI test backlog item: validation.md, pre-mortem.md, architecture-review.md, adversarial-review.md. Cross-artifact consistency review surfaced 3 blockers (AC #1's N/method never decided, a citation to a "Success Metrics" section that didn't exist in this project's own requirements.md, and a stale file path) — fixed by adding this project's own Success Metrics section (N=20, distinct decision from the sibling project) and correcting the path. Pre-mortem's 3 P1 items (unfolded review concerns, un-rebutted build-vs-buy divergence, unenforced AC#4 evidence gate) are folded directly into plan.md and research/build-vs-buy.md rather than left as unread siblings for Phase 5. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(sdd): build-vs-buy research for backlog-self-resolve Evaluates GitHub verification (extend existing github/ + session.ParseGitHubURL, no google/go-github), duplicate-marking helpers (none exist, build new), and CAS precondition mechanism (reuse TransitionBacklogItemStatus as-is). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): requirements + research for backlog-self-resolve Phase 1-2 planning artifacts for backlog item da58b867 (report_duplicate MCP tool + request_review CAS generalization). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): implementation plan + ADRs for backlog-self-resolve Phase 3 planning output for the report_duplicate MCP tool + request_review CAS-precondition generalization (item da58b867). 11 epics / 26 stories / 51 tasks, one Given-When-Then example per FR1-FR10, and 4 ADRs covering the GitHub-verification dispatcher/auth/error-classification design, the TriggeredByAgent audit-attribution scope, report_duplicate's idempotency rule, and the (flagged, owner-confirm-pending) decision not to extend FR2's active-reviewer refusal to report_duplicate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): resolve ADR numbering collision with concurrent planning session A second, independent SDD planning pass turned out to be running on this same self-referential backlog item concurrently — its ADR-001/ADR-002 files appeared in decisions/ between this plan's mkdir and git add. Renumber this plan's four ADRs to ADR-005-008 to stop clobbering the other session's numbering, and flag the one substantive disagreement found (github.GetPR HTTP addition vs. keeping the gh-CLI GetPRInfoCtx path) as unresolved in both plan.md and ADR-005 for whoever picks up implementation to reconcile. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): reconcile ADR collision and fix verification-notes overwrite bug in backlog-self-resolve plan Resolves a duplicate-execution artifact from planning: two independently-produced ADRs disagreed on GitHub PR-verification (gh CLI vs HTTP). Adopted the HTTP-only approach (ADR-002) per this repo's subshell-avoidance convention and the auth-consistency risk pitfalls.md flagged; deleted the superseded draft and renumbered ADR-001..005 sequentially. Also fixes a real bug a second research pass surfaced: report_duplicate's verification-notes write must append rather than overwrite, or it silently discards evidence from an earlier request_review call on the same ItemSession. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): repair review blockers in backlog-self-resolve plan Architecture review (1 blocker, 3 concerns) and adversarial review (1 blocker, 9 concerns, 5 minors) findings recorded and repaired: - Fixed FR10's stuck-item detector citation: pr_pending_no_pr only covers items with NO PrNumber (wrong shape for this feature); the correct existing coverage is ReconcilePRPending's PRReadyUnmerged/PRNeedsFix family, which operates on pr_pending items that DO have a PR reference (this item's actual scenario). No new detector needed, just the right citation. - Fixed verifyGitHubRefExists's Domain Glossary/GWT wording, which claimed a (bool, error) contract matching verifyPR when the actual designed signature is single-error-return. - Rewrote Task 4.2.6a's garbled concurrency test to actually call both handlers, and corrected Story 4.2.6's acceptance criteria (the "exactly one status-event row" premise was false for two legitimately sequential transitions). - Ratified ADR-003/ADR-004 to Accepted; tightened ADR-005's argument so it doesn't contradict Story 3.1.2's own independently-justified 4th refusal condition. - (Already applied by a prior pass: hasActiveReviewSession export instead of a 4th duplicate copy — confirmed no import cycle; idempotency substring- match fix; verifyGitHubRef injectable test seam; corrected test-file citations to server/services/backlog_github_rpc_test.go's resetGhBaseURL.) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): fix residual review inconsistencies in backlog-self-resolve plan Scoped re-review confirmed both prior blockers resolved. Cleaned up two leftover contradictions the re-review flagged: Story 3.2.2's narrative line still described verifyGitHubRefExists as verifyPR's (bool, error) shape, and Task 3.1.2b still offered a non-functional plain-== alternative that can never match the actual persisted notes format. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): test validation plan for backlog-self-resolve (report_duplicate) Maps plan.md's Phase 4 test tasks (~22 functions across Epics 4.1-4.3) to FR1-FR10, confirms 10/10 requirement coverage, and identifies 8 genuine test gaps (G1-G8) found by cross-referencing architecture-review.md and adversarial-review.md concerns that aren't yet reflected as Phase 4 tasks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): consolidate CAS validation and fix fail-open bugs in backlog-self-resolve plan Accepted a concurrent fix pass addressing adversarial-review concerns: - Collapse the whitelist-then-pin CAS fix (pitfalls.md #0) from 3 independently duplicated call sites into one validateSelfResolveSource chokepoint, so a future edit reverting to item.Status directly is a visible diff, not a silent regression. - Fix FR2's active-reviewer guard: a ListItemSessions error was silently swallowed and fell through to allow the transition, defeating the guard's purpose. Now fails closed (ErrInternalError, retry wording). - Fix FR5's messaging: a ListItemSessions error was silently treated as "no active reviewer," risking the exact false "reviewer notified" claim FR5 exists to prevent. Now defaults to the conservative "next review pass" wording on error. - Enforce "refused before any GitHub call" as a tested property (inject a t.Fatal-on-call verifier into every refusal-path test) rather than relying on code ordering alone. - Add missing test coverage: duplicate_ref/reason length caps, and an explicit decision + test that cross-repo duplicate_ref values are allowed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): give FR8 a dedicated story in backlog-self-resolve plan Cross-artifact consistency check found FR8 ("no schema changes") had no story anywhere in the plan's 4 phases -- only a bare final-verification task outside any Epic/Story, despite Epic 4.2's header claiming to cover it. Promoted it into Epic 4.4 / Story 4.4.1 with an explicit GWT, and corrected Epic 4.2's header to stop overclaiming FR8 coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): pre-mortem + P1 auth-mismatch fix for backlog-self-resolve Pre-mortem found 1 P1: report_duplicate's HTTP-token GitHub auth (GITHUB_TOKEN/keychain) and FR10's designated stuck-item safety net ReconcilePRPending (gh CLI auth) are non-overlapping credential paths. A session configured only via `gh auth login` would have report_duplicate fail every call while its own safety net keeps functioning obliviously -- silently recreating the exact "stuck with no way to self-resolve" problem this feature exists to fix. Fix: classify a missing-token failure (existing ErrNotAuthenticated sentinel) with a distinct, explicit non-retry message instead of folding it into the generic transient-retry bucket, and surface the same guidance in the tool description and ADR-002's Consequences. This doesn't eliminate the auth-mechanism split (unifying it is out of scope) but makes the failure legible instead of an infinite silent retry loop. Also accepted a concurrent iteration-2 re-verification pass that closed the last tracking gaps from the prior repair cycle (untracked fail-closed tests promoted to real Phase 4 tasks, a refusal-test-range/success-test contradiction fixed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): reconcile stale verdict headers ahead of readiness gate adversarial-review.md's top-level Verdict still said BLOCKED after its own appended iteration-2 section concluded CONCERNS (the blocker was resolved, only the header wasn't updated). pre-mortem.md's one P1 item (auth-mechanism split) is now resolved per the prior commit's fix -- checked off with a resolution note. Readiness gate: 10/10 FRs covered in validation.md, no TODO/TBD, all 5 referenced ADRs exist on disk, no open blockers in either review, Migration Plan documented N/A (FR8/ADR-001), 0 open P1 items -- PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): fix stale F3 note in backlog-self-resolve pre-mortem Two triad review lenses (PM, Engineering) independently flagged this note as stale -- it claimed the ListItemSessions fail-open bugs were still unfixed, but they were fixed in a same-day earlier commit. Left the historical note in place with a resolution update rather than deleting it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * feat(session): add TriggeredByAgent audit constant Epic 1.1 of backlog-self-resolve: agent-initiated backlog transitions (request_review, report_duplicate) need to be audit-distinguishable from TriggeredBySystem (reconciler) and TriggeredByUser (human). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * feat(github): add GetPR/GetCommit HTTP existence checks + sentinel errors Implements Epic 1.2 of backlog-self-resolve (ADR-002): report_duplicate needs to verify a duplicate_ref (PR/issue/commit URL) exists on GitHub before mutating backlog state, with definitive-vs-transient error classification. - Add ErrGitHubRefNotFound (404) / ErrGitHubAccessDenied (401, or 403 without a rate-limit signal) sentinels in github/repos.go. - Retrofit GetIssue's existing 401/404/403 branches to wrap the new sentinels via fmt.Errorf("%w: ...", ...) — no behavior change for err != nil callers, just an additional errors.Is-checkable layer. - Add github.GetPR (repos.go) and github.GetCommit (new commits.go), both HTTP-only via the existing newGHRequest/ghHTTPClient/getGHToken machinery (not the gh CLI subprocess GetPRInfoCtx uses), so all three ref-verification paths share one auth mechanism per ADR-002. - Table-driven httptest.Server-backed tests for both new functions (github/commits_test.go, github/repos_pr_test.go) covering 200/404/401/403-no-Retry-After/403-with-Retry-After/429, using an in-package resetGhBaseURL helper mirroring the pattern in server/services/backlog_github_rpc_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): fix double-review-spawn bug found in a fresh pre-mortem pass A concurrent re-run pre-mortem verified directly against session/review_gate.go that ADR-005's justification for not refusing report_duplicate when a reviewer is already active -- "TriggerReviewForSession's own idempotency" -- was false: ReviewGateRunner.Run has no active-reviewer dedup check anywhere and would unconditionally spawn a second, concurrent review session for the same item. Fixed with the narrowest correct scope: Task 3.3.3b's TriggerReviewForSession call is now conditional on the same activeReview boolean Task 3.3.3a already computes for message branching, instead of reversing ADR-005 (which would make FR5's literal "still succeeds while a reviewer is active" text unreachable) or patching the shared spawnReviewGate/Run infrastructure (broader blast radius, affects every review-gate spawn path in the codebase, not just this feature). report_duplicate still transitions the item and persists evidence either way; only the redundant immediate spawn is skipped. Extended Task 4.2.5a's test to assert the trigger is/isn't called in each branch. Also accepted: a GHES-support gap (F2) explicitly triaged as an accepted P2 for v1 (github.com only, not blocking); a trust-boundary disclosure line added to the tool description (F5); and a re-derived validation.md superseding a draft written against an intermediate plan.md revision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * feat(mcp): generalize request_review CAS precondition + active-reviewer guard Epic 2.1 (FR1/FR9): add allowedSelfResolveSourceStatuses + the validateSelfResolveSource chokepoint (server/mcp/tools_backlog.go) so request_review's CAS precondition is pinned to the validated observed status (in_progress or pr_pending), never a hardcoded constant or raw item.Status. Populate BacklogItemPrecondition.Note, switch TriggeredBy to TriggeredByAgent on both source paths (ADR-003), and give a CAS-race loser a distinct non-retry message instead of the generic transition- failed text. Epic 2.2 (FR2): export HasActiveReviewSession from server/services/backlog_service_triage.go (was unexported) and reuse it in request_review's new pr_pending-only active-reviewer guard. Fails closed on a ListItemSessions storage error (INTERNAL_ERROR, never a silent pass-through) via a new listItemSessionsFn test seam on backlogHandlers, mirroring the existing verifyPRMatchesBranch/ resolveSessionBranch shape — session.Storage.ListItemSessions has no swappable-repository seam of its own (hard type-asserts to *EntRepository), so this was needed to make the fail-closed path testable without a real DB failure. Epic 4.1: the 5 pre-existing TestRequestReview_* tests pass unmodified (regression), plus 6 new tests covering the pr_pending success path, whitelist rejection (table-driven), both active-reviewer-guard branches, the fail-closed storage-error path, and the CAS-race-loser message (genuine goroutine concurrency, stable across 20 runs under -race). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore(sdd): soften ReconcilePRPending "unconditional" claim, add success-text assertion Architecture review's residual finding: ReconcilePRPending's IsPRMerged/GetPRStatus calls can themselves fail (revoked token, GitHub outage) and continue past the tick without marking anything stuck -- a pre-existing characteristic unrelated to this feature, but "runs unconditionally" overstated it. Also extends Task 4.2.1b to assert the affirmative "Reviewer notified" success text, closing a gap validation.md flagged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * feat(mcp): add report_duplicate tool for backlog self-resolve (Phase 3) Implements Epics 3.1-3.4 of the backlog-self-resolve plan: a new report_duplicate MCP tool letting a work session route a backlog item to review when it discovers the work duplicates an already-shipped GitHub PR/issue/commit, plus Epic 4.2's full test suite (24 tests). - Handler skeleton + FR6 refusal checks (SkipReviewGate, role, link, disallowed source status), reusing validateSelfResolveSource and services.HasActiveReviewSession rather than duplicating either chokepoint. - GitHub verification dispatcher (verifyGitHubRefExists) behind an injectable verifyGitHubRef seam, with the 3-channel error split (ErrNotAuthenticated / ErrGitHubRefNotFound / ErrGitHubAccessDenied vs. plain transient) per ADR-002. - CAS transition to review (never done/archived, ADR-001) with an append-not-overwrite VerificationNotes fix and ADR-004 idempotency (exact-retry no-op, differing second ref rejected). - FR5 success messaging + the safety-critical fix from Task 3.3.3b: the review-gate trigger is now conditional on !activeReview (TriggerReviewForSession has no dedup check against an already-active reviewer, so calling it unconditionally would spawn a second, concurrent review session). - MCP registration with explicit FR10 retry guidance. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * test(mcp): fix stale in_progress->pr_pending comment in sequential-interaction test Spec compliance sweep caught the mismatch: the fixture correctly seeds the item at "review" (matching SetBacklogItemPRAndTransition's real precondition, per the adjacent inline comment already explaining this), but the summary and step comments still described the plan's original, inaccurate premise. No behavior change -- comment only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * refactor(github): extract shared HTTP status classification helper repos.go's SearchUserRepos/ListRepoIssues/GetIssue/GetPR and commits.go's GetCommit each repeated an identical ~25-line 401/403/429/default classification block. Extracted classifyGHResponse (github/http_client.go) parameterized by notFoundMsg (empty for the two list/search endpoints, which have no 404 semantics) and a sentinels flag (false for SearchUserRepos/ListRepoIssues to preserve their existing plain-error behavior, true for GetIssue/GetPR/GetCommit to keep wrapping ErrGitHubAccessDenied/ErrGitHubRefNotFound). Also swapped bare int literals for http.Status* constants and fmt.Errorf (no verbs) for errors.New (staticcheck S1028) in the consolidated logic. Also, two unrelated nits flagged in the same review pass: - tools_backlog.go: renamed checkGitHubRef -> verifyRef to match the file's field->shortened-method naming convention (verifyGitHubRef field, verifyPRMatchesBranch -> verifyPR, resolveSessionBranch -> sessionBranch). - tools_backlog_test.go: renamed TestReportDuplicate_LoserGetsDistinctMessage_WhenRacingReportPRCreated, which is misleadingly named "Racing" despite testing sequential state-machine composition on one goroutine, to TestReportDuplicate_RejectsThirdCall_AfterSequentialReportPRCreatedThenReportDuplicate. Verified SearchUserRepos/ListRepoIssues behavior is unchanged: no existing tests cover them directly (confirmed via repo-wide grep), so verification is by diff review — classifyGHResponse(resp, "", false) produces byte-identical error strings to the removed inline blocks for every status code path, and go build/vet/test -race stay green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * fix(mcp): address Gate 2 review findings on PR #308 backlog self-resolve Fixes 5 confirmed MAJOR findings from the Gate 2 review pass: 1. TestRequestReview_ReportsDistinctMessage_WhenCASPreconditionFails called require.NoError from inside a spawned goroutine — t.FailNow() only unwinds that goroutine, not the test goroutine. Both racers now send {result, err} through the channel and every require/assert call happens in the main test goroutine. The same channel type is reused for the new report_duplicate CAS test (fix 3). 2. Added TestReportDuplicate_VerifyGitHubRefExists_DispatchesPRTypeToRealGetPR, the first test in the package that leaves verifyGitHubRef nil so the real verifyGitHubRefExists dispatch switch (PR/Issue/Commit -> GetPR/ GetIssue/GetCommit) actually runs, pointed at an httptest.Server via githubpkg.GhBaseURL (mirrors github/repos_pr_test.go's pattern). 3. Added TestReportDuplicate_ReportsDistinctMessage_WhenCASPreconditionFails, mirroring the request_review CAS regression test for report_duplicate's identical errors.Is(transErr, session.ErrPreconditionFailed) branch. 4. Added TestReportDuplicate_DoesNotTreatPrefixRefAsIdempotentMatch, proving a shorter ref that is a literal string-prefix of an already-recorded longer ref (.../pull/27 vs .../pull/272) is not misclassified as the idempotent no-op retry. 5. requestReview's verification-notes persistence overwrote VerificationNotes via UpdateItemSessionVerificationNotes, silently erasing prior evidence (e.g. from an earlier report_duplicate call) on the same ItemSession. reportDuplicate already had this fixed with an append pattern; applied the same fix to requestReview and added TestRequestReview_AppendsToExistingVerificationNotes_RatherThanOverwriting as a new test, leaving the existing TestRequestReview_PersistsVerificationNotesOnWorkSession assertions unmodified per AC9/FR9. All 5 pre-existing TestRequestReview_* tests still pass with their original assertions intact. go build/vet/gofmt/golangci-lint clean on server/mcp and github packages; go test ./server/mcp/... ./github/... -race all green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxSH3xUu3fyFnprsybnRiN * fix(github): address Copilot review comments on PR #308 Two real findings from the Copilot review, fixed: - classifyGHResponse's notFoundMsg 404 branch didn't drain resp.Body, preventing HTTP connection reuse on keep-alive transports. Drain it, matching the pattern already used in the 403 rate-limit branches. - resetGhBaseURL restored GhBaseURL to a hardcoded default instead of the captured prior value -- fragile if a future test changes GhBaseURL before calling this helper. Capture and restore the actual prior value. Three other comments (claiming a missing encoding/json import x2, and claiming server/mcp files are absent from the diff) were declined as factually incorrect -- verified against the actual file contents, a clean go build, and gh pr diff's file list. The review's own banner noted it "was unable to run its full agentic suite" and only reviewed 67/87 changed files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDFVDKhxFquFRroHL7mDCW * chore: trigger CI (no-op) * fix(mcp): deterministic read-barrier for request_review CAS race test Root cause: TestRequestReview_ReportsDistinctMessage_WhenCASPreconditionFails synchronized only the *start* of its two racing goroutines via startBarrier.Wait(). That doesn't guarantee both goroutines' fresh GetBacklogItem reads (which feed validateSelfResolveSource before the CAS write) complete before either write does. Locally this ordering held 20/20 under -race, but on CI's runners (more cores, different scheduling) one goroutine could finish its full read->whitelist->write sequence before the other's first read even executed — so the "loser" observed the post-write Status: "review" and failed the whitelist check (ErrInvalidArgument) instead of racing the actual CAS write (ErrPreconditionFailed / ErrInternalError), which is the behavior this test exists to exercise. This is a test-harness determinism bug, not a production bug: the CAS write itself is already proven race-safe by TestTransitionBacklogItemStatus_should_letExactlyOneWinnerThrough_When_TwoWritersRaceConcurrently. Fix: add an injectable getBacklogItemFn seam on backlogHandlers (mirrors the existing listItemSessionsFn/itemSessionsFor pattern — nil falls back to h.storage.GetBacklogItem, so all existing callers are behavior-preserving) and wrap it in the test with a sync.WaitGroup(2) read-barrier so both goroutines' reads must complete (both observing "in_progress") before either can proceed to its write. Verified deterministic with -race -count=50 (was flaky under real CI scheduling); full server/mcp package remains green. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
fit()<->ResizeObserverfeedback loop: a WebGL cell-width measurement mismatch (actual vs. expected px/col) meantFitAddon.proposeDimensions()never reached a fixpoint, so every resize observation re-triggered another sub-pixel resize, flooding the server withTerminalResizeRPCs and pegging CPU.What Changed
XtermTerminal.tsx): only schedulesfit()whenproposeDimensions()reports integer cols/rows that differ from the currently-applied size AND repeat on two consecutive ticks — closes both the sub-cell-jitter loop and boundary-flapping near an exact cell-width boundary.useTerminalFlowControl.ts):resize()skips sendingTerminalResize(and the follow-upcurrentPaneRequest) when the incoming(cols, rows)equals the last pair actually sent — independent of the existing 200ms time throttle.force: truethird argument, with regression tests asserting that literal argument at each call site.Number.isFiniteguards againstproposeDimensions()returningInfinity), sofit()has a stable fixpoint even when the WebGL glyph metrics disagree with the DOM measurement.XtermTerminal.test.tsx,TerminalOutput.test.tsx, anduseTerminalFlowControl.test.ts(32 tests) simulating sub-cell jitter, boundary-flapping, WebGL mismatch escalation, and both force-bypass call sites — each assertingfit()/RPC/dispose fire at most the expected number of times, not once per observed frame.project_plans/terminal-resize-fit-loop/(requirements, research, architecture/adversarial review, plan, validation, pre-mortem).Test plan
npx jest XtermTerminal TerminalOutput useTerminalFlowControl— 3 suites, 32/32 passing[XtermTerminal]resize/fit log lines fired in an 8s watch window.Container resized...->Terminal dimensions BEFORE fit...->Sending resize to server: 172x37->Sampler confirmed resize, fit applied: 172 cols x 37 rows), then zero further lines over the next 8s.Originally opened as TylerStaplerAtFanatics/stapler-squad#191; moved here per request.