Commit Graph
9 Commits
Author SHA1 Message Date
Codeman maintainer 82593a4dd6 fix: keep remote file reads out of the login shell's parser (release review)
The remote file reads ran under the user's login shell, quoted for a POSIX
one. Under fish the probe was a parse error, so previews failed, and csh and
tcsh failed every multi-line read. Wrapping the probe in sh -c as the review
proposed would have opened a hole: fish reads \' inside single quotes as a
quote, so nested shell quoting flips, and a path holding ';touch x;' ran
touch x (measured, also on the existing folder listing). csh and tcsh run a
multi-line command line by line, so a newline in a path did the same.

buildRemoteFileCommand now sends every script as one word of printf text
with those bytes in octal, and only sh reads it. Probe, folder listing, read,
range read and transcript lookup gave the same result under bash, zsh, dash,
fish, tcsh and csh in a throwaway container ($SHELL -c, no real sshd), and 24
hostile paths ran nothing. Plain csh caps a word near 8 KB, noted in the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 07:40:07 +01:00
Codeman maintainer 81f8c8e894 fix: the live transcript lookup targets the exact tmux session (release review)
list-panes -t '=name' with no colon is a window target to tmux, so a
remote session that is gone resolved to a window of that name or to a
session the name is a prefix of, and the Edit history panel moved to that
session's conversation and stored its diffs. `=name:` is an exact session
match, as src/remote-pane-history.ts already uses. Measured on tmux 3.4.

The fake tmux in the test matched the name as a substring, so it passed
with or without the colon. It now needs the exact token and answers a bare
prefix the way real tmux does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:49:59 +01:00
Codeman maintainer 08d99da5ae test(edit-history): the ssh kill test no longer races the fake's pid file
The test waited for a real 1000 ms timeout and read the pid file afterwards.
On a loaded machine the kill could land before the fake ssh wrote it. It now
waits for the pid and sends the timeout's SIGTERM itself, which is all Node's
timeout does. Without exec the fake still survives and the test goes red.

The invariants said "the reads" go through buildRemoteEditHistoryCommand; the
image probes go through remoteProbePaths, on the same limiter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:17:14 +01:00
Codeman maintainer b5066744be fix(edit-history): a remote read with many subagents spans several round trips
The read script carries one case line per subagent cursor, and the whole
script is one argument of the ssh line, which Linux caps at 128 KiB. From
about 1000 subagents the command failed with E2BIG. The cursors have no cap
and the stored conversation is read first, so the session stayed unavailable
and not even a /clear got past it.

A read with more than 300 subagent requests now takes several round trips.
The cut is by name range, not only by count: the script takes a file no case
line names for a new one, so plain groups would read each other's files as
new. Each round trip walks only the names between its cuts, through one awk,
so a new file lands in exactly one range. Budget and meta budget carry over,
and a later round trip still lists its files, because a requested file missing
from the result loses its cursor. A failed round trip fails the whole read and
nothing of it reaches the store. A read of up to 300 runs no awk at all.

Each round of the 60-seed fuzz is also cut at a random size and compared with
the local reader. Run outside the suite too: bash --posix, BusyBox and mawk,
and bash 3.2 in a container, which is macOS sh. That shell cannot parse a case
inside $(...), so the names come from a shell function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:13:03 +01:00
Codeman maintainer 26b63aea2d fix(edit-history): remote lookup finds tmux, a timeout reaches ssh, docs follow
Applies the review of #766.

The live lookup ran tmux on the bare PATH of the ssh command. On a host
where tmux sits in ~/.local/bin or /opt/homebrew/bin it found no pane, the
2>/dev/null hid that, and the panel stayed on the launch conversation after
a /clear. It now goes through withRemoteTmuxPath(), inside the sh script and
scoped to the $(...), so the other tools keep the PATH they had. The fake
tmux of the test block moved to $HOME/.local/bin for the same reason: that
directory is first on the new PATH, so a real tmux is never asked.

The two ssh commands get exec. Node signals the local sh -c, and dash stays
the parent of a plain command, so ssh survived the timeout. A test with a
fake ssh shows the process dies now and survives without exec.

The comment at the meta budget claimed a meta past it comes with a later
read. A meta comes only with a subagent's first read, so it is never sent.

Docs: the store is named as the exception to "never copied to local disk",
remoteProbePaths and the remote transcript lookup are described, and the
line saying a remote session loads no store is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:57:58 +01:00
RandalixandClaude Opus 5.5 f27fda0f45 test(edit-history): the live conversation wins even when older; tighter wiring guard
Review round 3 (all low):
- the "written last" test left solo newer than conv-1 for the rest of the
  file, so the live test passed by mtime alone. solo's mtime is reset in a
  finally, and the live test now runs with the live transcript older than
  the stored one (a /resume into an older conversation before it writes).
- the server wiring guard matches the remote branch of the ternary as one
  expression, so swapped branches fail it.
- note on whole-second `-nt` (BusyBox, bash 3.2) at the lookup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-05 11:20:01 +01:00
RandalixandClaude Opus 5.5 5bcec26066 fix(edit-history): a remote lookup takes the conversation written last, not the first id's
Review round 2: with the stored conversation tried before the launch id, a
respawn that resumed the launch conversation after a /clear was missed once
claude exited again: the cleared-to conversation still had a file and won,
so the edits made after the resume were never read. No fixed order of the
ids is right both after a /clear and after such a resume. Without a live
conversation the lookup now takes the most recently written transcript of
all known ids (stored, claudeSessionId, id); the live one still goes first.

Also from the review:
- the remote no-transcript note and the docs name login profiles too
  (.profile, .bash_profile): sshd's `bash -c` reads neither, like .zshrc;
- a wiring guard pins that server.ts looks a remote transcript up with
  remoteTranscriptIds and leaves the local lookup on the hook path;
- the browser tests undo the injected remote session in a finally.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-05 11:20:01 +01:00
RandalixandClaude Opus 5.5 0a6a877415 fix(edit-history): remote reads get time for their size, follow CLAUDE_CONFIG_DIR, keep the conversation after /clear
Review findings on the remote edit history:

- A forward read may carry 13 MiB and "Load older" 29 MiB; the flat 30 s
  read timeout cut those off below ~1 MiB/s. The timeout now adds the
  transfer time at 256 KiB/s (remoteEditSourcesTimeoutMs). It ends a hung
  read, it does not cut a slow one.
- The transcript lookup looked only under $HOME/.claude. It now uses an
  absolute CLAUDE_CONFIG_DIR of the ssh command's environment, else
  $HOME/.claude, for both the projects and the live sessions/<pid>.json.
  A value set only in .zshrc stays invisible (the lookup is a plain sh);
  documented, and the panel's remote no-transcript note says so instead
  of promising the next prompt (a remote host sends no hooks).
- After a remote /clear claudeSessionId stays the launch id, so once the
  remote claude exited the lookup fell back to the cleared conversation
  and the panel flipped back. The sync now hands the lookup the stored
  conversation, tried right after the live one (remoteTranscriptIds).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-05 11:19:52 +01:00
RandalixandClaude Opus 5.5 c4ba14235f feat(edit-history): read remote (SSH) claude sessions, never waking the host
The edit history panel now covers remote claude sessions. The sync stays
path-based; its deps get the session and server.ts routes them to the local
reader or to two new ssh reads in remote-files.ts:

- remoteLocateClaudeTranscript: one round trip; the conversation the remote
  pane's claude writes now (~/.claude/sessions/<pid>.json via the pane's
  process tree, since remote hosts send no hooks), else claudeSessionId,
  else the session id, newest $HOME/.claude/projects/*/<id>.jsonl.
- remoteReadClaudeEditSources: one round trip per read pass, with the local
  reader's semantics (offset clamp, block end at offset+len, new files read
  their tail, shared main-first budget, onlyRequested, 8 KiB meta cap).

So /clear catch-up, "Load older", cursors and the store LRU work remotely
unchanged. Reads use buildRemoteFileCommand + runWithRemoteSshLimit only:
a sleeping host is a connect timeout -> unavailable, stored entries kept.

Tests: both scripts run on a real sh against a temp HOME; the read is
compared byte for byte with readClaudeEditSources on 60 seeded random
transcripts. A transitive import guard pins that the edit-history read path
reaches neither remote-wake.ts nor session-routes.ts. Browser: a remote
session lists images without thumbnails, a click previews. Docs updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-05 11:19:46 +01:00