test: pin the double-quoted PATH literal of withRemoteTmuxPath() (dash 0.5.11+, bash and busybox pass the shell cases unquoted)
docs: note in architecture-invariants that a new remote pane inherits the widened PATH from the creating tmux client
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Give a client that fails the M-1..M-9 guard its key back (send-keys <key> as the if-shell else argument), so a browser tab's Esc then digit reaches its pane instead of being eaten, also after a TUI killed before restore(); only M-1..M-9 are accepted since the key is now interpolated into that command
Test it: the unit test pins the else argument and the key check, and the real-tmux test runs cat -v in alpha and waits for the other client's Esc 2 to echo there, both before and after the TUI dies
Say so in docs/architecture-invariants.md and docs/tui.md, and reword the killed-TUI and failed-arm notes that said the keys do nothing
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test: record sendInput('-x hello') and sendInput('-\r') and check the shell argv ends -l -- <text> on both send-keys -l branches
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test/tile-grid-sse-filter.test.ts: search for the renamed 'const eventSource = new EventSource(' end marker and require both ends, so the connect-URL check covers that block again instead of the rest of app.js
test/sse-connect-timeout.test.ts: cover the replaced-EventSource guard in _handleSseConnectionFailure() (a late error from a replaced attempt leaves the new one alone)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
disarmTuiSwitchGuard matches the closed `#{client_tty},<tty>}` form, so a TUI on /dev/pts/4 no longer removes the hook armed for a second TUI on /dev/pts/41 (unit test plus a real-tmux case)
beginAttachHandoff arms the one-shot attach hook as its last tmux call before spawnSync, after the switch keys and the per-session dressing loop, with an order assertion in tui-app.test.ts and a sentence in the invariants paragraph
docs/remote-sessions.md and the buildRemoteLaunchCommand JSDoc show `remain-on-exit failed` at the end of the remote create chain
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs: document the SSE connect timeout next to the staleness watchdog in src/web/CLAUDE.md and docs/architecture-invariants.md (clear order, replaced-EventSource guards, offline handler leaves the timer)
test: rename the offline case to "connectSSE() while offline cancels the pending timer" so it names the path it drives
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
src/tmux-manager.ts: escapeTmuxTrailingSemicolon() JSDoc says every send-keys -l passes -- before the text, and its example uses the -l -- form
test/tmux-submit-paste.test.ts: the trailing-semicolon wiring test matches -l (?:-- )? so it pins only the semicolon rule
test/tmux-submit-paste.test.ts: master's two recorded-command tests for typed text expect the -l -- form (merge interaction with the inputRunner() refactor)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
findPersistedSessionOrFail now requires req (dropped the `?` and the dead `req &&` guard), like findSessionOrFail
The AST test in test/ownership-scoping.test.ts pins both declarations and asserts req's type is exactly FastifyRequest
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A user tmux config that leaves the prefix table empty makes
unbind-key -a -T prefix fail and drop later commands in the \; chain,
including send-prefix and remain-on-exit failed. Bind C-q send-prefix,
clear the table, then bind it again.
Also accept macOS ttys in the TUI switch-guard test without reading
/proc, and describe the client_pid hook escape as defensive on tmux 3.4.
Co-authored-by: DDT <1786035110@qq.com>
Docker and owned-remote tmux servers kept the stock prefix table, so Ctrl+Q
then )/(/s/w/L switch-client'd the inner client onto a sibling. Those create
chains now leave only C-q send-prefix.
codeman tui bound M-1..M-9 in the server-wide root table. The keys stay, but
switch-client runs only for the attaching client (tty plus a one-shot pid).
A browser tab's Esc-then-digit no longer jumps sessions, and a killed TUI
leaves a binding that matches no live client.
Co-authored-by: DDT <1786035110@qq.com>
findSessionOrFail took `req` as optional, so the quiet default was the
unscoped lookup: a route that forgot to pass it compiled cleanly and, in
multi-user mode, returned another user's session. `req` is now required,
and a caller with no request in flight must use the explicit, greppable
findSessionUnscopedOrFail instead.
Every current caller in src/ already passes req (verified by grep and
tsc), so behaviour is unchanged; the new helper has no production
callers and exists so the next request-less caller does not make `req`
optional again.
Tests pin the unscoped helper's behaviour and assert, from the source
AST, that `req` is a required parameter (tsc does not typecheck test/).
Second merge of #674: the author pushed the thumb-arc handback from the review themselves (e8fc08be), byte-identical in code to the merge-time fix already here, so this brings their round in and keeps the browser test and docs added at merge.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
# docs/wiki/Mobile-Guide.md
# src/web/public/terminal-ui.js
# test/session-swipe-gesture.test.ts
Conflict in CLAUDE.md's State Files line resolved by keeping #661's new
config-backup entries and this PR's "case default groups" in state.json.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts in CLAUDE.md, README.md, docs/api-reference.md and
src/web/server.ts were additions beside #662's Copilot quota read and
#666's keep-warm sections; both sides kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A scroll whose first move past the tap slop leaned sideways (a thumb arc) locked as 'swipe' for the whole contact, and SwipeHandler drops a swipe at 50 px of vertical drift, so the gesture did nothing. Past that limit the drag is now a 'y' scroll. Also brings the mobile-handlers fileoverview to the current 500 ms / 50 px limits and notes in the Mobile Guide that a selection pauses the swipe and a wide terminal pans instead.
Merge-time fix for #676. The test for nit 7 changed the worker's real cwd,
which also throws under a threads pool; a process.cwd spy gives the route the
same case-folder cwd with nothing to restore by hand. It still fails without
the workingDir gate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#670 stamped the preamble with the version it expected to ship in, 1.43.1.
The release is 1.44.0, so the stamp would name a version that never
existed. Any new value makes cached preambles from before #670's sendwait
change stale, which is what the stamp is for.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Squash of PR #658. Discovery records each llama.cpp model's parallel slot
count (from --parallel/-np, or /props total_slots when the launch command
states none), and a Claude launch warns when the context split across
those slots leaves less than 40K tokens per request. An explicit
--parallel beside a /props context records a slot count of 1, so a later
round overwrites an earlier count, and a non-2xx /props is remembered per
launch command while a network error is retried.
Merge-time fix by the maintainer: the architecture-invariants sentence on
the explicit --parallel case now says it records a slot count of 1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #676 (review finding 3 and nit 9). The State Files entry
for state.json names the new caseDefaultGroups key, and the Owner tab layouts
paragraph says where a case's default session group sits in the placement
order of sessionCreated() and that only TabLayoutService reads or writes it.
The wiki endpoint map counts the two new case routes (37 to 39).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Squash of PR #662. The header plan-usage chip gains a Copilot row: the
calendar-month premium-request quota, with the request counts and the
reset in the tooltip, read every 10 minutes from GitHub's
copilot_internal/user endpoint with the Copilot CLI's own sign-in token.
It rides showPlanUsageLimits and is on by default while the CLI is
installed and signed in; any failure leaves the chip without the row.
Merge-time fixes by the maintainer: the gates (setting, then token, then
the CLI probe) live in readCopilotPlanUsageIfEnabled() so a host with no
Copilot sign-in never runs the login-shell probe each tick, with a test
that Plan Usage Limits off means no request; the X-GitHub-Api-Version
header the Copilot CLI itself does not send is dropped; and CLAUDE.md,
architecture-invariants, README and copilot-integration describe the read.
The hooks-config import in server.ts was merged with #666's.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #676 (review nit 6). Once a Case settings popover had been
opened, every adopted tab layout (each create, delete and reorder) replaced
both selects' options, even with the popover hidden and the group list
unchanged, so a session created elsewhere could reset a native dropdown the
user had open. _refreshCaseDefaultGroupOptions now returns early when the ids
and names are the same. A vm case pins it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #676 (review nits 4 and 5). sessionCreated forgot a stale
case default inside the try whose catch rolls the registration back; it now
does so after the try, once the session is committed, so the cleanup can
never drop a committed session from the live map. caseDefaultGroup (behind
GET /api/cases/:name/default-group) no longer deletes a stale id as a side
effect: it reports Ungrouped and leaves the value for the next creation to
forget. The two tests that expected the read to clear it now expect it to
write nothing; the creation test still pins the cleanup.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The remote-history PATH bootstrap that rode along in this PR was left out
at merge (it belongs in its own PR that routes every remote tmux call
through one wrapper), so 242bbfcd's title overstates what lands here.
Conflicts in CLAUDE.md and docs/architecture-invariants.md resolved by
keeping this PR's rewrite of the wheel/touch paragraphs and re-adding
#664's touch-forwarding sentence to both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #676 (review finding 2). POST /api/ralph-loop/start now
passes { caseName } to addSession so the session lands in its case's default
session group, and nothing tested it. The registration-order test asserts the
placement argument; it fails against the route without the change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #676 (review finding 1 and nit 7). The Git worktrees modal
launches Claude through POST /api/sessions with workingDir
<case>/.claude/worktrees/<name>, which inferLocalCaseName did not recognise,
so the session landed in Ungrouped while the same gesture for any other CLI
(quick-start with the case name) landed in the group. The inference now
strips a trailing .claude/worktrees/<name> lexically before its two checks,
reusing CLAUDE_WORKTREE_PATH (hoisted to module scope). It also runs only for
a named workingDir, never on the server's own cwd, as the hook and skill
injection above already do. Route tests for both, failing without the fix;
api-reference and architecture-invariants say so.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An EventSource whose request never gets response headers stays in
CONNECTING without firing open or error, so the backoff never runs and
the staleness watchdog (which only judges a 'connected' stream) cannot
see it. Bound each attempt with a 15s connect timeout: if open has not
fired, close the attempt and retry through the same path onerror uses.
The timer is cleared on open, on failure and at the top of every
connectSSE(), and both the timeout and the failure handler ignore an
EventSource that has since been replaced.
`send-keys -l <text>` keeps parsing options after `-l`, so text starting
with `-` was read as a flag ("unknown flag -x", "invalid flag --") and
never typed. End option parsing with `--` at every send-keys -l site in
sendInput() and sendInputToPane(), as set-buffer already does.
Merge-time fix for #660. CLAUDE.md and architecture-invariants still said a
raw npx vitest skips setup.ts, contradicting the updated Testing note: both
now say it resolves the root vitest.config.ts (and why that file must stay).
The architecture-invariants entry for safeRmHomeTree adds the sandbox-HOME
requirement (isSandboxedHome, TEST_HOME_PREFIX), the same rule for
isUnderTestHome, the route-harness assert and the 2026-10-10 incident.
CLAUDE.md's repo-root preamble lists vitest.config.ts with its reason, so a
tidy-up does not move it into config/ and reopen#640. The
assertSandboxedHome JSDoc no longer claims every route test is covered: only
the ones built on createRouteTestHarness are.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #660. afterAll runs under vitest's 10 s hookTimeout, and
each failed removal attempt costs rmSync's own retry sleeps (50 + 100 + 150
ms) plus the 250 ms wait, so 20 attempts overran it: with a writer still
filling $HOME/.claude/backups, the file failed with "Hook timed out in
10000ms" after its test passed, and the "leave it to the exit backstop" branch
was never reached. The loop now stops after about 5 s. Checked with a
temporary (uncommitted) test that leaves a child process writing there for
20 s: before, the file failed at 11 s with the hook timeout; after, it passes
in under 6 s.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Squash of PR #669. Browser terminals could be moved onto another session
by tmux's prefix keys: Ctrl+B then ), (, s or w switched the tab's tmux
client to a different session while the tab kept the original's name.
Codeman's tmux socket now keeps only Ctrl+B Ctrl+B (send-prefix) in its
prefix table, and codeman tui and scripts/tmux-manager.sh get F1 as their
way out instead of Ctrl+B d.
Merge-time fixes by the maintainer: on an empty prefix table the
unbind-key that fails no longer skips binding send-prefix; a key tmux lists
escaped (prefix C-\ shows as C-\\) compares equal, so the reset does not rerun on every create;
and reconcileSessions() reduces the table at boot when the socket has live
sessions, so sessions that survive an upgrade are covered too.
Co-authored-by: christianhaberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #661, as promised in the reviews. The long Config backups
paragraph in CLAUDE.md moves into a new architecture-invariants section that
gives each rule with the reason behind it (allowlist, volatile keys, retention
by successor and the date floor, the origin id, retention after a bad or
reset settings.json, the bounded probe, one walk per pass, verify before
write, the undo copy and the partial-restore answer, the reload hooks,
redaction, admin only). CLAUDE.md keeps the short rules and a pointer, and the
State Files list names config-backup-id and backups/config/ with the
retention each manifest records.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #665.
- A markdown link converted to a preview link became `a.rv-path`, so its
label ("Our incident record") took the path style: monospace, 0.92em
and `word-break: break-all`, which split prose mid-word on a phone. The
anchor now also carries `rv-path-link`, and
`.rv-text a.rv-path.rv-path-link` resets font and word-break, as the
File Viewer does for its labelled document links.
- The first new test adds `/tmp/a.json?raw=true` (pins the `?query`
strip) and `/tmp/` (pins the trailing-slash folder check, since
findFilePathLinks finds no folder in a bare `/tmp/`), and checks the
new class.
- The `_linkifyFilePaths` JSDoc and docs/architecture-invariants.md say
the function also rewrites anchors whose href is a scheme-less,
non-`//` path, on purpose without FILE_PATH_LINK_PATTERN's root and
extension filter.
- Mobile Guide: "a web link" in the response viewer opens a tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #661. The pre-restore copy was forced, so a restore made
while the live files equalled the newest backup spent a retention slot on a
duplicate. It now goes through the normal change check: when nothing changed
since the newest backup, no copy is made and safetySnapshot names that
backup. Two tests that relied on the forced copy now change a file before the
restore; a new one pins the reuse.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>