- docs/api-reference.md and the voice-stream.ts overview now say a
transcript frame is the current utterance so far and a final frame
ends it, as the relay's own comment and the invariants doc do.
- VoiceInput.cleanup() hides the Send button before stop(true), so the
button stays up for the words stop(true) salvaged and inserted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- follow lastPickedCase (written by saveLastUsedCase while sync is on), not lastUsedCase, so a quick-start or an agent's spawned worker no longer moves other devices
- close the open case-options popover when the follow or an import changes what it shows; follow through selectQuickStartCase(save:false)
- failed save reuses _noteSettingsWriteResult and says the choice lasts until the next sync
- rename undo renames the shared entry back instead of restoring the whole map; lastPickedCase follows a rename
- caseSettings and lastPickedCase added to the redaction, rename and backup-volatile lists and the docs
The fixes#529's review asked for, each with a test.
- No inline handler. The button quoted the path inside
onclick="...decodeURIComponent('<path>')", and encodeURIComponent leaves
' alone, so a file named x'-alert(1)-'.txt ran script in Codeman's
origin and it's.txt was a syntax error. It is now bound in the existing
.file-tree-item loops from the row's escaped data-path.
- Symbolic links are refused, never followed. validateSessionFilePath()
returns the realpath, so deleting AGENTS.md -> CLAUDE.md deleted
CLAUDE.md and left the link dangling. The path as named is lstat'ed,
a link answers 400, and the inode is compared again before the unlink.
- The edit route's confinement whole: validateLocalSessionFilePath()
behind withGuardedOutsideWorktree(), so an absolute path in a followed
outside worktree obeys the same rules, and the sensitive, blocked and
.git checks run on both the realpath and the path as named.
- Adopted sessions are refused like remote ones (the /tmp exception of
the older file surfaces is not copied to a new one); Docker workspaces
are allowed, as on the edit route. The workspace is probed bounded
first (#516). Errors are thrown with their status.
- Deleting from search results re-runs the search, the row buttons stay
visible on touch screens, the confirm and toasts have zh-CN text, and
no button is drawn for remote or adopted sessions.
- Docs: CLAUDE.md, architecture-invariants, api-reference and the
Working With Files wiki page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved as on test/desktop-macbook: config/test-suites.ts keeps both browser tests (desktop-connect-page and local-model-ui), and the CLAUDE.md state-files line keeps #778's cli-installed entry with the local model's local-model.json and local-model/ entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runner header says what each caller passes (the job: the compiled stock
command; the route: the merged entry, behind cliManagementEnabled).
docs/cli-registry.md names both executors in its trust boundary. The
security architecture gains a section on the pinned runtime and its hash,
the loopback port without a login and the digest check, and the install
path without cliManagementEnabled; SECURITY.md lists the port as a known
limitation. The guides and the rules name exactly the paths the launch
guard covers, the session that keeps calling a stopped runtime's port, the
claim before the connect writes, and the outcome kept in memory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/local-models.md is the guide: the steps, the layout on disk, what is
written outside the folder, the failures and their next steps. The API
reference documents the install object and the five verbs, and how the list
is ordered. The rules for the server side are in CLAUDE.md and
architecture-invariants.md, the user-facing pages in the wiki.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep the 1.49.0 API meaning: liveWorktree and root=live stay inside the
session folder, and an outside worktree is published as outsideWorktree
and walked only with root=live-any.
Refuse a symlinked .git folder as the main worktree, sensitive files
inside a followed worktree, and any outside worktree while attachment
confinement is on or around a blocked tree. The file routes re-check
the guard settings on every request, and a throwing rule check refuses.
The panel keeps its Back to worktree button while pinned to the session
folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#772 made the Files panel follow an agent into a worktree inside the
session's folder. A worktree beside the repository (git worktree add
../feature, or an EnterWorktree into one that already exists there)
stayed unfollowed, since serving it needs a second confinement root.
findOutsideWorktree() answers that case without running git. The
nearest .git above the pane must name <common dir>/worktrees/<id> of the
session's own repository, and that registry entry must name the
candidate back (or the .git folder must be the common dir itself). It
refuses /, the home folder and above, any folder holding workingDir and
Docker case workspaces, and multi-user mode applies the owner's
folderAllowedForUser rule. The answer is liveWorktree with outside: true
and the folder's real path.
The file routes serve an absolute path inside that worktree, and only
while the folder still resolves to the recorded path. A relative path
still resolves against workingDir alone. The browser routes such paths
to the editable preview, and root-relative markdown refs resolve from
the top of the worktree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/api-reference.md: GET /api/local-model is labeled experimental
until the step that sets a model up ships, so its payload is not a
stable /api/v1 contract one stage before that step changes it. The
section now describes the verdict as judged, the three sentences of
the memory reason, the notes, the speed rounding and which size to
show as the installed memory. The os-fallback description was wrong:
the free disk space is reported there too.
- docs/versioning-policy.md: the route is listed under the experimental
surfaces.
- docs/wiki/Settings-Reference.md: App Settings > Models > Model on this
Mac, in the words of a user.
- CLAUDE.md and src/web/CLAUDE.md: the invariants the code relies on
(read-only, the probe, harness rows as data, the meaning of limited,
the word Recommended, one unit, labels from the payload, when the
card and the group show).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A "Local model recommendation" section in the API reference: the read-only
route, its admin gate in multi-user mode, why a failed probe is a 200, and
every field of { hardware, recommendation, catalogUpdated }, including that a
size travels as bytes and as decimal GB text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A subfolder whose bounded probe answered `unknown` came back as `children: []`
with no `incomplete` and `truncated` false, so a dead mount, or a healthy one
slower than the 1.5 s probe on first touch, read as an empty folder and a
search missed it in silence. `unknown` is never `absent` (#516).
Such a folder now goes on the walk's `unread` list, which flags the node and
sets `truncated` in both walks. A folder the filesystem says is gone drops out
quietly as before. The row said "file limit reached", which would be false
here, so it now reads "Not fully scanned" for either reason.
Reproduced on a real disk with a parent at mode 0444, where the child's stat
answers EACCES: the API, the search and the panel all report it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The field had no mention. It opens the sandbox's network and does not touch
codex's approvals, so it sits beside the sentence about them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session named codeman-* or claudeman-* on any socket is dropped with no
note, which contradicted the docs. paneCwd is absent for an adopted session,
and only a claude wrapper reports statusLineExporter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adoption section said every git route answers unsupported, but git-diff
and git-fetch-settings answer 400, and the Git status section listed only
remote and docker. Edit history's `adopted` reason was documented nowhere.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clone Repo's picker now offers New folder, and with the field empty Browse
opens in the cases folder, so the first press there lands on this refusal.
It pointed at Add Case > Create New, which cannot clone. The sentence has to
read right from every picker that offers the button, so it names both tabs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`POST /api/sessions/:id/run` had no adopted gate, so while the wrapper had no
attached PTY it spawned `claude -p --dangerously-skip-permissions <prompt>`
in /tmp, a launch every other adopted route refuses.
The route answers INVALID_INPUT, and `runPrompt()` refuses too, which also
covers the legacy `sendInput()` path and leaves no direct spawn in Session
that an adopted session can reach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wrapper that could not start fell back to a direct PTY: `claude
--dangerously-skip-permissions` or a server-account shell in /tmp on this
host, under a tab still carrying the foreign session's name, so someone who
opened an ssh or container session ended up typing into this machine.
startInteractive and startShell now fail the start for an adopted session
and leave `_useMux` on, so selecting the tab tries the wrapper again. The
`error` event carries the reason, since the browser's re-attach does not
read the route's answer. A session with no mux at all is refused the same
way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The attach line now takes the rule #770 gave remote and docker launches: bashCArg() single-quotes it at both respawn-pane sites, so it reaches bash verbatim. quoteAdoptLaunch is retired, since its double quotes kept the same $ and backtick safety but lost a real newline. #510's no-cd condition stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
candidatesFrom had moved outside the try when the transports moved to
runForeignProbe, so a classifier failure would have reached the route as a 500.
The module promises it never throws, so the guard is back for local and docker.
Docs:
- The api reference said `blocked` waits are always refused, and listed Ralph as
refused while two of its file routes were not (they are now).
- The invariants doc and CLAUDE.md gain the rules this review turned up: target
syntax in names, the ping before list-panes, nothing typed into an adopted pane,
quoteAdoptLaunch.
- CLAUDE.md no longer says every claude session gets hooks or a stop wait.
- ForeignTmuxSession's mode and workingDir describe the first listed pane, not
the active one.
- The wiki had no page on adoption: The Dashboard gains an Opened outside Codeman
section, and the remote and docker pages point at it. docs/docker-cases.md now
separates an adopted container from an adopted tmux session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The round-robin walk capped what it listed, not what it read: every queued
folder was read whole, sorted and held until drained, so one GET could pull
millions of entries (100 folders of 50,000 files killed a 400 MB heap). A read
now keeps only the entries the listing can use, and MAX_WALK_ENTRIES_READ stops
opening folders at 250,000 entries, leaving the rest queued and flagged.
A folder also spent its whole share on its own files, leaving nothing for the
subfolders it had just found, alone in its round or beside a sibling, so search
hits inside them were lost. Its files now count as one more share beside those
subfolders. Math.max(directoriesInRound, 2) would have fixed only the sole-folder
case and cut a wide root's folder list short.
Folders below the walk root are probed before their readdir (#516), since the
walk reaches folders the depth-first one never did.
Filtering at the read also clears the false flag on a folder whose only unread
entries were hidden or excluded, and an exactly-full workspace now finishes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the folder field empty, Create New's Browse opens in the cases folder, and New
folder there answered "Use Add Case > New Case". There is no tab of that name (the
label is Create New), and a Create New user is already on it. The refusal now reads
right from Link Existing and from Create New's custom folder. Its test pin, the code
comment, the manual, api-reference, CLAUDE.md and architecture-invariants move with it.
The block() helper in the New folder picker test returned an empty string for a name
that is not in session-ui.js, so .not.toContain passed and a renamed picker dropped out
of the check. It now asserts the name was found.
The manual's path picker list said two places and left out Create New, which the New
folder paragraph names. It now lists every Browse that opens the picker, and that
paragraph is rewrapped to the page's width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two routes are public under /api/v1, so they get an API reference section.
The overlay gets an architecture note and a CLAUDE.md rule: an adopted session
has session.remote and session.docker undefined and a neutral workingDir, so any
new code that reads workingDir as a workspace must also test isAdopted. That is
the class of bug this round fixed a dozen times.
Records a measured limit: while the view exists an owner's kill-session leaves
the window alive under it until the tab closes.
Comments that still called workingDir the foreign pane's cwd now say what is
true since the wrapper got its neutral folder. Also drops a zh-CN key that
duplicated an existing one, and updates the two structure tests pinned on the
lines this round changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The fair rounds give a directory its node before reading it, so every folder
still queued when the 5,000-node budget ends sat in the tree with empty or
partial children and read as an empty folder. Keep the node on the tree cursor
and set an additive incomplete: true on every cursor left in the queue, then
draw one dim, inert row under such a folder while it is expanded. The envelope
check ignores keys it does not know, so old and new clients stay compatible.
Stop each round at the top once the budget is spent, before the shift: the
rest stay queued unread, and flagged, rather than read and sorted for nothing.
Say what the walk is: a per-directory round-robin (breadth-first), fair per
directory and not per subtree, with search results now shallow-first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API reference still said a registry failure on create and clone is
500 and did not list the rename's new 409s. CLAUDE.md said both files
are restored after a failed registry write, and left out the
config-backup restore exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The security wording claimed a folder strictly below workingDir was the whole
argument, which the symlink fix showed was only the lookup's half. The rest is
docs that were missing: liveWorktree, root=live and worktree in the API
reference, the mechanism section the CLAUDE.md paragraph had no pointer to, what
a root=live query matches against, and the limits a reader meets in the wiki
(the Git panel stays on the session's folder, a session started below the
repository's top is not followed, a page under .claude/worktrees opens as
source).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Consolidates the four contributor changesets, and rewords the em-dashes this release
added in comments and docs. The five in test/settings-save-failure-ui.test.ts stay:
they assert existing toast text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The startup hint is no longer typed, a Discord reply is one of the input paths that
set fromUser, edit-history is a state file, POST /api/filesystem/mkdir is in the API
reference, and the store's header says which routes really refuse it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner decision. Typing the note into a brand-new non-Claude tab spent a model
turn per tab and, for a spawned worker or a scheduled run, landed with the first
real prompt. Claude keeps its hint through the system prompt, and every other CLI
is told nothing. The setting goes with the path it gated: sessionIdle, the fresh
set, isHintableSession and the session:idle wiring. sendInput loses `fromUser`,
which was only ever false for the hint, and the deps test pins that a reply is a
person's own input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cwd` beside the fixed `workingDir` reads ambiguously on a public API surface, and
`paneCwd` says where the value comes from, like `paneExit`. Also pin the field on
the payload the browser really receives (getSessionStateWithRespawn), and give the
pane-directory text its own architecture-invariants paragraph.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-four review smaller items: api-reference now states the opposite-of-apiKey behaviour for an omitted defaultContextLength, and the wiki sentence follows the code, modelContextLengths[id] ?? defaultContextLength: any stored figure beats the default.
- share one context-length resolution (discovered figure, endpoint default fallback) across the routes and boot recovery, with a recovery test
- record listed per-slot context figures with a slot count of 1 and force the slot-count fallback once per rediscovery
- open up the codex model launch token for qwen3-style ids (model-plain-colon), pinned by a spawn-golden case
- pass the full discovered window to opencode as limit.context
- translate the picker search strings and document the discovery precedence
A direct clone whose git-slot wait timed out came back with no spawnError, so
the cleanup emptied a folder git never touched. GitRun now says whether git
started and the cleanup requires it. Cloning into a folder at or inside a
Docker case workspace is refused, as git status, fetch and worktrees do, since
git would run the new repository's hooks and filters on the host.
A direct clone into an empty folder emptied the folder after any failure, which
deleted a concurrent attempt's finished clone when git had written nothing.
Clones into one folder are now mutually exclusive in-process, the cleanup is
skipped when git refused the folder, never ran, or the mount is unresponsive,
and a name linked while the clone ran is a 409 that keeps the folder.
Documents path for POST /api/cases/clone.
- lone digit rule in CLAUDE.md, invariants, user docs, wiki, API reference and code comments
- say which installs have the codeman command
- reload() comment back above reload(), merge the sendInput comments, typedHint in the hint comment
- drop the stray dot after --urgent in the startup hint
- config-backups wiki: name the discord.json token, not the URL
- comma in the CLAUDE.md auto-named list
- codeman notify exits 1 when the route answers 200 with data.ok false
- notify request times out at 30 s instead of hanging
- fileProblem probes the file with the bounded probe before realpath
- the thread map records its parent channel; a new channel starts afresh
- threadFor returns its failure instead of sharing this.last
- discord-notify: per-session token bucket (6 a minute), 429 RATE_LIMITED, plain line from codeman notify
- fileProblem probes the working folder with the bounded probe before realpath
- sendTest uses its own DiscordApi and leaves this.cfg alone
- drop the resumed-tab check from the typed hint (only Claude's field was read)
- first sessionRenamed per session records the name without a PATCH
- boot goes through ensureLoaded, so one gateway starts
- a thread created without an id reports the failure
Claude keeps the silent system-prompt hint (startupHint, on). Other CLIs are sent the typed message only with typedHint, which costs a model turn per tab.