The wiki gains right-to-left notes for Last Response, the phone's Compose
editor and the rendered Markdown view, plus a Troubleshooting entry for the
#715 report: the terminal is xterm.js, which has no right-to-left support
(xtermjs/xterm.js#701). src/web/CLAUDE.md and the invariants now state the dir
pins, the logical-property rule and that the direction pass runs last.
The changeset claimed every file path stays left to right. It now says only
what holds: the paths the viewer links on its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_renderMarkdown` wrote dir="auto" into its string, before the code pins and
the linkifier. An element carrying dir="auto" re-resolves its direction on
every insertion under it, and a block whose letters all sit in `dir` children
walks its whole subtree each time, so a code-only or path-only document went
quadratic, English ones included.
Measured in real Chromium through the real File Viewer, before and after:
9000 inline-code items 8.7 s to 0.35 s, 9000 bare paths 5.6 s to 0.23 s, a
209 KB out-of-workspace list 41.9 s to 1.4 s. All three now match master.
The pass is now `_applyBlockDirection()`, run last on the DOM by both
consumers. The direction results are unchanged. Tests pin that the rendered
string holds no dir="auto" and that the pass runs after the linkifier.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the real trigger and CODEMAN_NO_ADMIN=0 in the READMEs and the
Installation page, add the install without git to the update tables and the
updater notes, record the installer's downloads in the supply chain section,
and say that the data directory question also removes a kept runtime.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An account sudoers lists by name, with a password, is routed to no-admin
because nothing can read sudoers without reporting to the administrator.
The route itself stays as chosen, but the reason no longer claims the
account may not use sudo, and every message that ends or sums up the route
names CODEMAN_NO_ADMIN=0 for that reason alone.
tmux and cloudflared are staged in the home folder, so a noexec /tmp no
longer reports a working build as one that does not run. USER gets a default
at the top of the script, which also covers the older lines this route can
reach. A trailing slash on CODEMAN_INSTALL_DIR no longer puts .new and .old
inside the install. The data directory prompt names a runtime kept one
prompt earlier.
The uninstall check reads the source and must never drive uninstall(): HOME
does not redirect systemctl --user, so a drive reaches the real service.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The summary and README said rerunning the script updates Codeman. It never
fetches, so that was a successful no-op. The summary, README, its zh-CN mirror
and the wiki now say git pull, then rerun, with the in-app route first.
Also corrects "printed once" (twice at a terminal, never off one), says a typed
answer beats a preset, adds the first-run setup, the 180 s wait, --no-wait and
the changeme refusal to Running-As-A-Service and Troubleshooting, and records in
architecture-invariants why docker/.env is generated FROM .env.example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With stdout captured (CI, provisioning, `| tee`) the log held the password twice
for a service that controls Docker on the host, while docker/.env, the stronger
copy, is 0600. Off a terminal the script now points at the file instead. The note
beside it no longer promises a second showing, which `--setup-only` never gave.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusals of $HOME, ~/.codeman and the checkout compare strings, so
`~/../me` or `<repo>/../repo/data` passed them: the first shares state with a
native install, the second lands in the build context, where `COPY . .` would
bake CLI logins into the image. Doubled slashes are collapsed and any `.` or
`..` segment is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first-run gate tested `! -f`, so a dangling symlink was silently replaced,
and a directory got a 0600 temp file holding a working password, a "Wrote"
message and exit 0, with setup repeating on every later run. Setup now runs only
when nothing is there. A symlink to a real file stays an existing install.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CODEMAN_APPDATA_PATH read ended awk at its first match. Whatever wrote the
rest of `config --environment` then met a closed pipe, and pipefail ended the
script with exit 141 and no message. Real Compose wins that race (0 of 50 runs,
on 2.33.0 and 5.5.0), so nobody has reported it, but the test stub, once it
honoured the shell environment, lost it. Same read as the password check below.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a single-quoted dotenv value a backslash escapes the closing quote, so a
password or data path ending in one passed validation and produced a docker/.env
Compose cannot parse. The error named an unrelated line, printed the password,
and a rerun never asked again. The env_quote comment claimed a single quote was
the only character such a value cannot hold, and the test stub's comment said
bash reads these values the way Compose does: both corrected, and a test now
reads generated values back by Compose's rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compose prefers the shell environment over the env file, so with
CODEMAN_APPDATA_PATH or CODEMAN_PORT preset, a different typed answer went into
docker/.env and the summary while the stack ran on the preset: state split
across two folders and the next plain start mounted another home. Setup now
unsets everything it wrote, so the file it just printed is what Compose reads.
The test stub honours that precedence, and the questions get their first test
through a real pty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docker info --format` exits 0 with the daemon unreachable on those clients,
so a user outside the docker group got a raw Compose error and a "network
hiccup" hint after docker/.env and the data folder had already been written.
`docker version --format` exits 1 on every release and keeps the same stderr
text, so the permission branch still matches. The test stub now answers `info`
the way those clients do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The New folder rule in the same paragraph does cover that picker, so "none of
the host rules" said too much.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picker is now reached from every Add Case Browse button, so three lines
in the manual and the opening of the CLAUDE.md and architecture-invariants
paragraphs read wrong. A Browse fills its form field, only the Path key types
into a prompt, and Docker's Container Workdir picker lists the inside of the
container through POST /api/docker-cases/browse: it always shows dot entries
and the host allowlist does not apply to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cases-folder refusal grew to 143 characters, and the status line is one
line with an ellipsis and no title, so at 513px on desktop it ended at "or
make the case with Ad..." and lost the instruction. .path-picker-status.error
now wraps: 2 lines on desktop, 4 on a phone, measured clear in Chromium at
both, and it covers the picker's other refusals too.
Tests: a pin on that rule (fails without the fix), and the option check now
reads each picker's whole options object instead of a fixed 400 characters,
which stopped short of the container-workdir picker's 829. Retitled to say
what it checks: Create New has the flag too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Nothing is copied to the Codeman host" stopped being true when the Edit
history panel started keeping diffs read from a remote session's
transcripts. The README, the wiki's Remote SSH Sessions page and its
Security page now name that exception, in the wording the PR already uses
on Working With Files and in remote-sessions.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Prettier wants single quotes on the package key, as earlier changesets in this
repository have. Valid YAML either way, so the release is unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex now declares a version gate too, so the notes in session.ts and
docker-hosts.ts were wrong, the hooks-config caveat described every local codex
when --no-daemon now covers 0.162 and later, and session-routes left network
access out of the launch defaults. architecture-invariants gains --no-daemon and
the network config in its list of Codex flags. Comments and docs only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setting told users the sandbox and approvals stay on. With it on, a
command in Codex's sandbox can call Codeman's API, which can start a session
outside the sandbox with no approval prompt. Measured on codex 0.162.0 with
`codex sandbox`: a touch the sandbox refused landed through POST /api/sessions,
and from inside a Codeman pane it landed with a password set too, because a
pane inherits CODEMAN_PASSWORD. Without that password the same call gets 401.
The settings row, the wiki, the skill and the changeset now say so, the wiki
Security page and security-architecture name it, and the docs scope the claims
to what the code does: app-wide, local launches only, not cron jobs, and
--no-daemon is a local launch's flag. Agent-CLIs gains the --no-daemon
trade-off. A test pins that no surface sells the sandbox as still in charge.
Text only: the switch, the clamp and the launch are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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 owner's answer to the PR's open question: the switch does not also pass
--sandbox workspace-write. Settings, wiki, skill and release note now state
the consequence: a folder Codex runs read-only keeps the network closed and
asks per command. A golden test pins the decision by name, and the stale
launch-defaults comments now name networkAccess.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in src/session-cli-registry-bridge.ts: kept the PR's gateVersion() (gates measured against the gated CLI's own version) and added master's Discord startup hint engine value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tcsh login shell worked on 1.48.0 only because the local sh rewrote
${SHELL:-/bin/sh} to this host's shell. With the launch verbatim, tcsh gets -i -l,
refuses it and every stock launch leaves a dead pane. The changeset and
docs/remote-sessions.md described that as a standing limitation; they now name it
as a change in this release, with what the override workaround costs.
Also: extraSshOptions joins the list of values now literal at launch, the stock pane
command is shown as /bin/sh -c in the docs and the builder's JSDoc, the invariants
state how the launch must reach the far end, the manual pages cover $ in an override,
and the invariants no longer say extraSshOptions refuses a bare $ (it never did).
Reworded the em-dashes on the invariants line this rewrites.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the limit on a session with its own CLAUDE_CONFIG_DIR, which does not
carry its conversation across an endpoint because the isolated projects link
goes to the server's tree. Also records that an own home set before a restart
with no selection is lost on clear, that only an absolute path is persisted,
and tells the user manual what a clear gives back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ownerClampedEnvKeys() read enabledClis(), so disabling a CLI dropped its
privileged keys from the clamp, and a revoked owner got a saved home back on
recovery or reboot restore. getCli() still returns a disabled entry, and a
session can outlive the switch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getCustomModelForPersist() trusted homeEnvVar to name a path, but the registry
types it as any env name, so a clis.json override could write a session's own
credential to state.json. Only an absolute path is written now.
A re-apply resolved the CLI's real home from what the session's map held, which
under a selection is the injected directory, and recovery resolved none at all.
An entry the own home lacks (settings.yaml) was then linked from the server's
home on an endpoint switch and on recovery. The route now reads the new
Session.ownEnvValue(), and recovery reads the clamped record through the same
bounded guard, under the bulk cap since it walks every saved session. Also
corrects the comment that said only a removed directory gets its links again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate comment in terminal-ui.js said a stale-false flag falls through
to the PageUp/PageDown fallback, which is false for pi since the second
commit of this PR removed that fallback. The FOOTGUN comment above it made
the same claim. Say what happens instead.
"Every pi below 1.0.0 emits no mouse enables" was too absolute: regular
was the default there, but fullscreen exists from 0.84.0. Measured with
--tui-mode fullscreen under tmux on 0.84.1, 0.85.0, 0.86.0 and 0.99.2:
alternate_on=1, mouse_any_flag=1, mouse_sgr_flag=1. Default settings on
0.99.2 report all 0.
Document that Wheel Scrolls Local History leaves the wheel dead in
fullscreen pi (wiki, integration guide, changeset), add a pi block to
Troubleshooting, cross-reference the accepted exception from the dead
gesture rule in architecture-invariants, and mark the pi-integration-plan
passages this PR reversed as superseded.
Comments and docs only, no runtime change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteCommandOverridesSchema is .strict() and stopped at deepseek, so the API
refused a commands.omp or commands.copilot override although both modes can run
remotely and in a container (RemoteCommandMode, DockerCommandMode) and the launch
builders honour the key. The docs call the override the way out for a login shell
the stock launch cannot run, which was not true for those two modes.
The test walks the CLI registry, so the next CLI cannot miss the schema the same way.
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 rename's checks inside mutateLinkedCases and the refused-registry
branches of rename and clone had no test: removing them left every
case route test green. A hook right after the folder move lands a link,
an unlink, a re-pointed entry or a torn file between the route's first
check and its registry write. Each new test fails when its check is
removed.
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>