Seen on a MacBook (M5 Pro, 2026-11-12), in a real Claude Code session: the lead started a
Pi worker and an OpenCode worker on the local model in ITS OWN case, sent both their tasks
at once, and reported afterwards that one written answer was lost. Both prompts had named
the same file, codeman-answer-<token>.md, and the second worker's summary replaced the
first one's. The code the workers wrote was not affected.
The token was the second plus $RANDOM. Claude Code runs zsh on a Mac, and zsh hands every
subshell the same $RANDOM until the parent shell reads it (measured there: four subshells,
one number, where bash drew four). The token line is itself a subshell, so under zsh the
"random" part never changed, and two calls in one second shared a token. Workers in their
own cases never noticed: same name, different folders, and the marker is matched per pane.
The token now ends in six characters of the worker's id. The test makes $RANDOM a plain
variable that always reads the same, which is what zsh does, and runs two calls at once.
The reference gets one sentence for hand-built markers in a fan-out.
No new preamble version: 1.51.0 is not released yet.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Conflict in skills/codeman/SKILL.md (and its plugin mirror): the list of
modes with neither stop nor blocked gained jcode here and the omp
exception from #798 on the release branch; both are kept.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The four edits promised in the review of #798:
- An omp alert could clear while omp's dialog was still up. omp items got
the same pane `capture` as Claude's, which the inbox re-reads with
Claude's numbered-dialog parser. omp's dialog has no numbered rows, but an
agent's plan above it does, so the list was parsed as the item's options,
and once it scrolled out of the frame the item was resolved as answered in
the terminal. An approval-only item now takes the frame once as `context`
and gets no `capture`, like a screen-read item (web/screen-blocked.ts), so
the module's `elicitation_complete` is the only thing that clears it. A
route test puts a numbered list above an omp-shaped dialog and fails on
the old code at both steps (options parsed, alert cleared).
- The reboot-restore install is dropped: the restore plan skips every CLI
whose transcript is not `claude-jsonl` (omp's is `omp-jsonl`), so it never
ran, and it sat after the start, when omp has already scanned its hooks.
- The docs that say which CLIs raise `blocked` and open alert-only items now
name omp: api-reference, CLAUDE.md, architecture-invariants, an entry for
`approvalHookFile` in cli-registry, and the skill with its plugin mirror
(verbs.md listed omp among the modes where `blocked` is a 400, one line
above the paragraph that says it works).
- Two comments: the schema's file-name rule is a pattern, not a closed list,
and the marker check takes the marker anywhere in the file.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
dsh-TUI 0.19.0 opens a setup guide ("Step 1 of 4") in every session
until a person has finished it once, and the guide's cursor is the same
glyph the skill waits for as the composer: the readiness mark matched on
it in 2 of 2 launches. An Enter there acts on the highlighted step,
which is a sign-in. A spawned worker was handed back "ready" on that
screen, and the first task prompt would have been typed into it.
After the mark, the rendered pane is now read once: a worker that shows
the guide is deleted and the spawn fails with what to do (finish the
guide once from the Run menu). For `local:deepseek` and for a plain
`deepseek` worker alike.
Also says which verb reads a local DeepSeek worker's answer: `sendmark`.
The harness reports the end of a turn, but dsh 0.2.2 renamed its
transcript file and Codeman reads an empty answer from it for now.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
`spawn_worker <name> local` started the one agent the model was
installed with. The server now lists every agent the model can be run
with, so the skill passes the choice on:
- `local_model` adds `agents` (each with its status, whether it is the
default, and the readiness mark the server measured) and `models`
(what is on the Mac; only the active one runs). A new reason,
`no-agent`: installed, but nothing that can run the model is there.
- `spawn_worker <name> local:<cli>` (`local:pi`, `local:deepseek`,
`local:opencode`, `local:claude`, also as `name:local:pi` in
`spawn_workers`) starts an agent whose status is `ready`. Anything
else fails the spawn before a session exists and names the agents
that are startable. No agent is ever installed from here: that stays
a person's click in the window. A Codeman that lists no agents knows
one, and only that one can be named.
- The readiness mark comes from the server when it names one, else from
what this skill measured (OpenCode `┃`, Pi `extended-keys`), and
`LOCAL_READY_MARK` overrides both.
- A Pi worker on the local model is started with
`piConfig.approveProjectTrust: false` (--no-approve). On a custom
model Pi keeps no home, so it asks to trust the folder in every
session with Trust preselected, and the task prompt's Enter would
answer that.
Pi's mark is the last line of its startup, a warning about tmux's
`extended-keys` option. Its composer rule is painted in the first frame,
before Pi takes Enter: a prompt sent at the rule stayed unsent, 2 of 2,
and one sent at the warning was submitted, 4 of 4 (pi 1.5.2, Linux). It
is a side effect, not a contract, so a Pi that stops printing it fails
the spawn and the worker is deleted, never handed back on a guess.
The preamble changed, so CODEMAN_PREAMBLE moves to 1.51.0 in the
preamble, the SKILL.md copy of it and every version check, and the
plugin mirror is synced.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
jcode (1jehuang/jcode) joins the CLI registry as the `jcode` run mode:
Run menu, phone overview, TUI, quick-start and POST /api/sessions
(`jcodeConfig: { model?, resumeSessionId? }`), local, SSH and Docker
(dedicated agent-image layer), plus codeman doctor and install.sh
through the generated catalogue. Measured on a live jcode 0.98.1 pane.
- Work detection reads jcode's braille status row and `<n>>` composer;
alt screen with mouse strip and PageUp/PageDown paging like opencode.
- Every jcode TUI is a client of one shared per-user daemon whose tools
run with the daemon's env, so no agent skill injection and no JCODE_*
envOverrides prefix.
- New `capabilities.sharedDaemon`: a session kill spares descendants
outside the pane's process group, so closing the tab that started the
daemon no longer cuts every other jcode session off (measured).
- last-response answers an honest empty for an external CLI with no
transcript reader that the pane segmenter cannot read (jcode alone).
- GET /api/jcode/status via the generic registry resolver.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The opencode readiness and marker measurements were made on Linux, not
on a Mac, and the docs now say so. A worker whose pane ends after it
wrote its answer file but before the marker matched answers 3 with no
answer: the rc 3 row now says the file may still be in its folder. The
example's input folder is named input, so the prompt reads cleanly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on opencode 1.3.0 in a private tmux socket, with a fake
OpenAI-compatible endpoint streaming the reply a character or two at a
time: the marker was on the screen, but the stripped client stream read
D, spinner, O, spinner, N, ... because opencode redraws its spinner
between the streamed pieces, so wait-output never matched it. sendmark
then waited its whole timeout and answered 2 for a reply that had
finished, with the answer file already written (opencode's write tool
wrote it without asking).
The marker wait now runs in rounds of at most 10 s, and between rounds
the answer file is a way out too: a non-empty regular file whose
checksum held still for one round is read, with matched false. The
marker stays the fast path. The answer-file wait after a marker no
longer spins when the exit wait gets no answer, and treats an ended
wait as the worker gone.
The same run measured the readiness mark: with a fresh home and a
custom provider, the first composer bar came with the composer and a
prompt pasted with its Enter at that moment was submitted, so the docs
say so instead of "not measured". The example keeps its input in a
subfolder, apart from the case's CLAUDE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sendmark's comment and verbs.md 5.5 listed six external modes by name,
and test/agent-skill-mode-lists.test.ts refuses any partial list of them
(the one a ninth CLI would silently miss). Both now say what the class
is: a hook-less claude, a worker on the local model, and every external
agent mode but deepseek.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md gets a subsection after the DeepSeek Harness workers: why to hand
work to the model on this Mac (bulk work with no per-token bill, files
that stay on the machine, a second opinion, a worker that needs no
network), the copy-paste block (local_model, spawn_worker local, sendmark,
delete_session), and the limits as the code has them: a person installs
the model, one request at a time, slower than the cloud, markers not
hooks, a spawn can start a runtime the person stopped, admin only and
experimental.
The description now names the local model, so "use the local model"
reaches the skill. Section 2 gains a row, the hook-less row points at
sendmark, and reference/verbs.md gets 5.16 with every local_model reason,
the spawn steps and the sendmark exit codes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The codeman skill's preamble learns the model Codeman installs on this Mac:
- local_model asks GET /api/v1/local-model and prints one JSON line: the
installed model, the coding agent that runs it and its endpoint, or the
reason there is none (not a Mac, nothing installed, an unfinished
install, a blocked install, admin only, a server without the routes).
Exit 0 only when a worker can start now or after a runtime start.
- spawn_worker <name> local (and name:local in spawn_workers) starts a
stopped runtime the way the window's Start button does, quick-starts the
installed agent with customModel, and waits for its composer (OpenCode's
bar, the claude path for a claude agent, LOCAL_READY_MARK otherwise). A
confirmation question from quick-start fails the spawn with empty stdout;
nothing is ever confirmed on the person's behalf.
- sendmark <sid> <prompt> drives any agent worker without an end-of-turn
signal: the exactly-once input body, a split marker through wait-output,
then the answer read back from a file the worker wrote in its folder
(regular files only, 256 KB at most), with one exit code per outcome.
The preamble stamp moves to 1.50.0 so every cached copy is rewritten.
The tests run the real preamble against a stubbed server.
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 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>
Measured on 1.47.0 with the same "spawn a shell worker, echo, read, delete"
task: DeepSeek Harness and OpenCode already pass end to end; Codex 0.162
failed three ways, fixed here.
- Start Codex 0.162+ with --no-daemon (new noDaemonFlag gate, fail-closed).
The shared app-server daemon stopped every launch on an "incompatible
feature settings" dialog when another version's daemon was running, and
ran commands with another session's CODEMAN_* environment.
- Evaluate registry version gates against the gated CLI's own version
(resolveSessionCliVersion) instead of claude's for every entry.
- Add the opt-in synced setting App Settings > Codex > Allow network access
(codexNetworkAccess, default off): --config
sandbox_workspace_write.network_access=true, filled as a launch default
and clamped off for non-granted owners like the bypass switch.
- Skill: carry on with the curl path when `codeman` is not on PATH, re-run a
sandbox-blocked call once through the CLI's approval path, name the new
setting. Wiki and registry docs updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved by keeping both sides:
- CLAUDE.md (Agent CLI): #694's opening (an HTTP client, the experimental
agent-session-routes behind agentCoordinationEnabled) with #708's clause
on the directory spawn and its two refusals.
- docs/api-reference.md (codeman agent): #708's spawn paragraph and --json
shape, with #694's inspect/stage/submit in the verb list and the guarded
endpoints named before "adds no route for the other verbs".
- src/cli-agent.ts (@fileoverview): #708's quick-start / directory spawn
routes and #694's experimental routes in one endpoint list; the two
node:fs/promises imports (#708's realpath+stat, #694's open+stat) merged
into one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`codeman agent spawn` always sends its directory as an explicit
workingDir, so the server cannot apply its no-workingDir rule and
installs workspace hooks (and an injected skill) into ~/.claude when the
caller's cwd is HOME. A bare spawn whose cwd resolves to the home
directory now refuses with exit 4 before any request and names
`--working-dir ~`; an explicit `--working-dir ~` is still sent. Paths
are compared by realpath, so a symlinked HOME matches, and an
unresolvable HOME or cwd falls back to the resolved path without
throwing.
The directory-spawn --json shape now reads the same in the README, the
API reference, the wiki page and the changeset: `ready` when a
readiness wait ran, `readinessError` when that call failed, and
`startupError` when the start failed. A test pins all four.
`codeman agent spawn --working-dir "$WT"` with WT empty (a failed
`git worktree add`, say) resolved '' to the caller's cwd and started an
editing worker in the caller's own checkout. An empty or
whitespace-only --working-dir now refuses with exit 4 before any
request.
The emitJson comment names the directory-spawn exception to "the
envelope's data": `{ sessionId, workingDir, created: true, ready }`.
Tests: unit (`''`, spaces, tab: exit 4, no calls) and through the real
argument parser and HTTP transport (nothing sent, exit 4).
Docs: README, api-reference, wiki, SKILL.md (plugin mirror synced) and
the changeset note the refusal.
`codeman agent spawn ""` (for example `spawn "$CASE"` with CASE unset)
used to launch a worker in the caller's cwd. Both case checks now test
`caseName !== undefined`, so an empty name reaches quick-start and is
refused by its schema as before.
Drop the `{ id }` fallback on the create answer: `POST /api/sessions`
answers `{ session: { id } }`, and the test fakes now use that shape.
Tests: an empty case name (unit and through the real argument parser
and HTTP transport), the claude `--ready` order create, /interactive,
wait-output on the created id, and a non-JSON failed start (id alone on
stdout, reason on stderr, exit 1).
Docs: the spawn mapping and directory-launch JSON shape in
docs/api-reference.md, the README and wiki `codeman agent` sections, the
cli-agent @fileoverview and the skill reference tables; reflow the new
SKILL.md paragraph (plugin mirror synced); the changeset notes the
local exit-4 refusal of an unknown mode for named cases.
A coordinator can leave text for a worker without typing into its pane.
- Store (src/web/agent-inbox.ts): read until ack by id, seen only for the
receiver, 16 KiB/message, 512 KiB + 200 messages per inbox, full = 409
CONFLICT (never drops the oldest), 12 h TTL from each message's createdAt.
- Routes (routes/inbox-routes.ts): POST/GET /api/sessions/:id/inbox
(?wait= parks while nothing is unseen), POST .../inbox/ack (receiver
only, 403 otherwise), GET /api/agent-inbox/summary (owner-scoped counts,
not in toState()). Caller = resolveParentSessionId().
- Inbox waiters are the wait registry's third kind: same caps, ended by
cancelAll/cancelEverything.
- Opt-in snapshot (agentInboxPersistEnabled, synced, default off):
agent-inbox.json via dataPath(), 0600 tmp+rename, off deletes it, boot
prune only after a successful restore. Dropped on delete, kept on detach.
- CLI: codeman agent post | inbox [--wait] | ack, INBOX column in ls.
- waitCapacityResponse/abortOnClientHangUp moved to route-helpers.ts
unchanged.
- Docs: api-reference, README, CLAUDE.md, architecture-invariants, skill +
plugin mirror.
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>
A prompt made only of blank lines joined to nothing, and sendwait then sent
a bare Enter onto whatever sat on the worker's composer. It now refuses
with "sendwait: empty prompt" (the test fails without the check).
reference/verbs.md still said sendwait takes one line and a multi-line
prompt can sit unsent; it now says sendwait joins the lines with a space,
which loses their layout, so long multi-line content belongs in a file.
Plugin mirror regenerated.
Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
sendwait posted its prompt as is. With the mux now keeping line breaks, a
multi-line prompt goes in as a paste, and right after spawn_worker returns it
can land inside Claude Code's Enter-ignore window. Neither the Enter loop
here nor the server's submit check recognises a pasted block that is still on
the composer, so `Review this:\n- item 1` sat unsent while the re-wait ran
out. sendwait now trims each line, drops blank ones and joins the rest with a
space before building the body, the same result as toSingleLine, like every
other waiting sender.
The preamble stamp moves to 1.43.1 so cached preambles pick the change up;
the plugin mirror is regenerated with scripts/sync-plugin.mjs. The new test
runs the real preamble.sh with CURL stubbed and fails without the join.
docs/api-reference.md gets the submit-check sentence the README and wiki
already carry.
Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The tab layout's 512 refs also count pinned sessions kept as stopped records, so
the ceiling holds at the default MAX_WEBVIEWS and with no pinned closed sessions.
SKILL.md, verbs.md (both copies) and the cron guide no longer give a fixed 50/25.
Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
TmuxManager.sendInput() removed every `\n` before the text reached tmux, so a
multi-line prompt sent with `useMux` (or a multi-line respawn prompt) arrived as
one line with its lines run together: `"echo A\necho B\r"` ran `echo Aecho B`.
The text half of a submit has gone in as one bracketed paste
(buildPasteTextCommands) since the Codex paste-burst fix, and inside the paste
markers Claude Code and Codex insert a pasted line break into the composer
instead of submitting. So the `\n` is now kept (`\r\n` loses only its `\r`) and
the existing paste carries the lines; the Enter stays a separate tmux call.
Multi-line text without an Enter is pasted too rather than typed with
`send-keys -l`, where every line break would reach the pane as a key.
Single-line input is unchanged.
The test-mode gate moves from the top of sendInput() into inputRunner(), as
readPaneRows() does, so a test subclass can record the commands sendInput()
would run instead of pinning the branch shapes on the source.
SubmitVerifier still compares the composer's last glyph line with the prompt
minus its newlines, so it does not reliably recognise a multi-line paste and
may not re-press an Enter that Claude Code ignored. `agent send` and the TUI
therefore keep refusing or joining multi-line input; the docs that described
the stripping now describe the paste and that limit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
Follow-up to the previous commit, after a fact check against the code.
- Answer surfaces: the header bell answers Claude prompts, the phone
strip only Claude dialogs (and only with the inbox on), and push
Approve/Deny only a Claude permission prompt (PUSH_EVENT_MAP gives
actions to permission_prompt alone). README, its Chinese mirror, the
notifications page and the phone guide now say so.
- Antigravity and Grok Build: after the 3 s launch settle, idle is armed
only by Claude's prompt marker and working only by a braille spinner,
so "idle when output stops changing" was wrong. The wiki, the grok
guide and the API reference now call their status unreliable.
- DeepSeek Harness: its idle and working reports become stop and
agent_working, which drive respawn, waits and the inbox but not the
session's own working/idle display. Keeping Agents Running says that,
and that the respawn path needs the status bridge.
- The wait section of the API reference no longer calls exit the only
dependable signal for screen-checked modes; the inbox page names how
Codex and DeepSeek items clear; Core Concepts and Hooks And
Integrations no longer call DeepSeek "the one" exception; Agent CLIs
notes that Codex dialogs still raise the red alert; the inbox design
doc notes the alert-only items.
- Skill: stop and blocked list deepseek, and SKILL.md no longer says
codex has neither a transcript nor blocked. Mirrored with
scripts/sync-plugin.mjs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#642 stamped the preamble with the version it expected to ship in, 1.41.2. The release is 1.42.0, so the stamp would name a version that never existed. Any new value makes cached preambles from the broken version stale, which is what the stamp is for.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude Code rewrites a skill body that it loads with arguments: every bare
$1, $2 ... becomes the matching word of the request (0-based). Nine lines of
the agent preamble used bare positional parameters, so an agent that pasted
the §0 bootstrap (every session without a pre-seeded preamble: Agent Skill
injection off, the default, or a remote or Docker session) wrote helpers
that queried session "5" with timeout=claude and looked up "5=" instead of
CODEMAN_PASSWORD= in the .env, sending every call unauthenticated.
- $1/$2 -> ${1}/${2} in preamble.sh and the identical §0 heredoc in
SKILL.md; same bash, and Claude Code's pattern (\$\d+(?!\w)) skips it.
- CODEMAN_PREAMBLE 1.33.4 -> 1.41.2, so a cache file written by the broken
bootstrap (which carries the old stamp) is rewritten on the next run.
- A comment at the top of the preamble states the rule.
- New test: Claude Code 2.1.300's substitution applied to SKILL.md with
sample arguments must leave the body, and the pasted heredoc, unchanged.
It fails on the old file with the exact sessions/5 and timeout=claude lines.
- plugins/codeman re-synced; patch changeset.
Verified with real Claude Code 2.1.300 (the delivered heredoc is now
byte-identical to preamble.sh; before, 9 lines differed) and by running the
pasted helpers against a local recording server.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The agent skill only reached Claude Code: injection wrote
.claude/skills/codeman, which no other CLI reads, and the per-session
preamble was seeded for claude alone. A Codex, Gemini, OpenCode,
Antigravity, Grok, DeepSeek or OMP session inside Codeman never learned it
could start and drive workers.
- Registry field capabilities.agentSkillDir (a closed enum, since
clis.json can set it and it decides where the server writes) names the
skills directory each CLI scans. Claude keeps .claude/skills; those seven
CLIs get the same skill in <case>/.agents/skills/codeman (each measured to
scan it; Claude Code does not, so it never sees two copies). pi gets none:
a project skill makes pi ask "Trust project folder?" at startup.
- The preamble is seeded for every local session whose CLI takes the
skill; nothing in it is claude-specific.
- The skill text is CLI-neutral and names the two CLI-side failures to
report instead of retrying: a sandbox that blocks loopback (Codex's
default), and commands that do not carry this session's environment
(Codex 0.162 runs them in a shared app-server daemon).
- POST /api/sessions injects after the launch pre-flight, so a CLI that
cannot start leaves nothing in the workspace.
- codeman skill install gains --mode; uninstall --case removes the copy
from every directory the registry can write.
- The settings toggle moves to Agents & CLIs > All agent CLIs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The agent skill teaches the session verbs to claude only (Codeman seeds its
preamble for local claude sessions). An opencode, codex, pi or gemini agent has
the same environment — CODEMAN_MUX, CODEMAN_SESSION_ID and CODEMAN_API_URL are
exported into every pane — and nothing that teaches it the verbs, so
`codeman agent ls|spawn|send|wait|read|interrupt|rm` packages them as commands.
It is a client of the server, like `codeman tui`, and stays out of
`codeman session` (which drives the in-process SessionManager). No new route and
no second transport: everything goes through CODEMAN_API_URL, so auth,
ownership and the per-session waiter cap apply unchanged. Credentials and the
Basic header come from src/codeman-credentials.ts, in the same order attach and
the TUI use.
Invariants, each in test/cli-agent.test.ts:
- Refuses outside a Codeman session (CODEMAN_MUX=1 + CODEMAN_API_URL); never
guesses a URL.
- `send` takes the prompt as ONE argument (an unquoted multi-line `$(…)` would
otherwise be split by the shell and re-joined into one line), transmits
printable text plus Enter only, and refuses multi-line input loudly instead of
letting sendInput weld the lines. No resend loop of its own: the server's
SubmitVerifier owns the swallowed-Enter case. ESC exists only as `interrupt`,
which never appends Enter.
- `rm` fails closed: empty id, an unprovable self id, or a prefix match in
either direction refuses.
- Nothing mode-shaped in the CLI: the readiness mark `spawn` waits for comes
from the registry (new `capabilities.composerReadyMark`: claude's composer
hint `shift+tab`, deepseek's `❯`), `read` relies on the route's own answer
dispatch, and a `stop`/`blocked` the session cannot fire is the server's 400,
passed through. A `/wait` timeout is a 200 with `timedOut`: exit 2 with a
neutral line, not an error. A worker that dies during spawn's readiness wait
is exit 3, like every other wait.
- `X-Codeman-Agent-Origin` rides only spawn's quick-start, the one request that
may create a case directory; every other verb leaves it off. Server-side,
test/routes/agent-case-marker-routes.test.ts pins that a POST /api/sessions on
an existing workingDir is never labelled, header or not, and that quick-start
labels only a directory it creates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
spawn_worker builds its quick-start body itself ({caseName, mode,
parentSessionId}), so an agent driving the skill had no way to give a worker
the advisor without hand-building the call and losing the readiness ladder,
hooks vetting and trust-dialog fallback. Setting CODEMAN_WORKER_ADVISOR
(fable / opus / sonnet) now adds `advisorModel` for every claude worker that
spawn_worker or spawn_workers starts; other modes ignore it.
A refused value fails the spawn with the server's INVALID_INPUT message. A
server without advisor support drops the field silently (the schema is not
strict), so spawn_worker reads it back and says so on stderr.
The preamble changed, so CODEMAN_PREAMBLE is bumped to 1.33.4 and stale
cached copies are rewritten instead of silently ignoring the variable.
SKILL.md's heredoc and the plugin mirror are synced.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first shape of the Enter loop read the composer BETWEEN two short waits,
and tested `wait.ended` (the session exiting) where it meant `timedOut`. A
`stop` that fired while no wait was open was lost, since signals have no
history, and a re-wait that had already resolved on `stop` fell through into
another wait that could never see the edge again: measured twice, the answer
was on screen and sendwait ran its whole 580 s slice anyway.
The long re-wait (a tagged duplicate of the original frame) is now registered
first and kept open in the background for the rest of the call; the loop reads
the composer and re-sends Enter beside it, stops when the prompt has left or
the wait's response has landed, then returns that response. Measured: the
stranded prompt got one extra Enter and sendwait returned on `stop` at 36 s.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude Code 2.1.277 takes typed text the moment its composer paints but
ignores Enter for the first 30 to 50 seconds after it (measured 2026-09-19
through the input route: an Enter at 28 s stranded the prompt, one at 51 s
submitted it). The text+Enter pair `sendInput` sends 50 ms apart therefore
left every programmatic prompt sitting unsent, and every waiter burned its
timeout on a turn that never started.
Server: `SubmitVerifier` (session-submit-verifier.ts), armed from
`writeViaMux` for every mux write that carried a carriage return, reads the
pane on a 2 s to 60 s schedule and re-sends Enter only while the last
composer line (the CLI's own prompt glyph) still holds the head of what was
sent. An empty composer, other text, or no composer line at all ends it; a
newer write replaces the schedule.
Skill: `sendwait` gets the same loop (`_composer_text`, no-break space
stripped by its bytes for BSD sed) for servers that predate this, and the
preamble version moves to 1.30.1 so seeded agents pick up the fresh copy.
SKILL.md's heredoc and the plugin mirror are regenerated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A long orchestration creates one case directory per worker and deleting the
sessions never removed them, so ~/codeman-cases accumulated scratch folders
that were indistinguishable from real projects. They are now labelled and
have a cleanup path.
- src/agent-case-marker.ts: a case dir quick-start CREATES for an agent-driven
spawn gets a .codeman-agent-case.json marker (when, by whom, parent session,
mode). Only the create branch writes it, so a linked case, a cloned repo or
any pre-existing path is never labelled; reading is total, so a malformed
marker means "not agent-created" rather than a half-trusted entry.
- The signal is the new X-Codeman-Agent-Origin header the skill preamble sets
on its shared curl (preamble bumped to 1.22.0), or an agentOrigin body
field, falling back to a resolved parentSessionId so a worker spawned by a
stale skill copy is still labelled.
- GET /api/cases publishes it as agentCreated; GET /api/cases/agent-created is
a read-only cleanup listing adding inUse and modifiedAt; Add Case -> Manage
badges each case and offers a review-then-delete sweep that names every
directory in its confirm and skips any case a live session is working in.
Removal stays on the existing DELETE /api/cases/:name.
- Agent preamble caches are collected too: ~/.cache/codeman-agent-<id>.sh was
written per claude session and never removed (236 leftovers measured on a
working machine). Now deleted with the session and swept at boot, guarded by
a live-session keep set plus a 7-day age floor.
Verified end to end on an isolated instance: marker written for header, body
and lineage-only spawns, absent with no agent signal and for a pre-existing
directory; inUse flipping on session end; badge, sticky bar, confirm and sweep
driven in a browser; preamble seeded on create, removed on delete, boot sweep
taking only the aged orphans.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every claude session in a directory claude had not seen before died about six
seconds after it started (`Pane is dead (status 1)`), before the agent drew a
composer. Reproduced on a fresh case and measured.
Claude Code 2.1.252 rewrote the dialog. It used to be
❯ 1. Yes, I trust this folder
2. No, exit
and is now unnumbered, reversed, and highlights the option that quits:
❯ No, exit
Yes, I trust this folder
Detection still worked (the confirm affordance carries the match once the
numbered option text is gone), so the failure was entirely in the answer: the
auto-accept pressed Enter on the highlighted default, which is now exit.
trustDialogNextKey() reads the ❯ marker off the rendered pane and returns ONE
keystroke at a time: an arrow while the cursor is on the wrong option, Enter
only once the screen shows it on the trust option, and null for a frame that
does not say. Both layouts are handled, and which way the trust option lies is
read from the frame rather than assumed, so a further reordering costs a
repaint instead of a session. The last marked option wins, because the
direct-PTY fallback reads an append-only buffer where an older frame must not
out-vote the freshest one.
Two things only a live pane showed:
- The scan ran solely from the PTY onData handler. The arrow that moves the
cursor is the last output the pane produces, so the first fix parked every
session with the cursor sitting on the right option and no Enter ever sent.
It now schedules its own follow-up read (_trustDialogTimer, cleared in
_clearAllTimers()), offset past the scan throttle so the chain cannot break
on a boundary.
- The keystroke cap goes 3 -> 6, since answering is no longer one press.
The bundled codeman skill had the same blind \r as its bounded fallback, so
preamble 1.21.0 replaces it with _trust_key/_accept_trust: read
terminal?full=1, steer onto the trust option, re-read, then confirm. Those
keystrokes go out under their own clientId, because input sequence numbers are
monotonic per client and spending prompt numbers on dialog keys would make the
next send-and-wait look like a stale duplicate and vanish while reporting
success. The readiness recipes in docs/extending-codeman.md,
docs/api-reference.md and the skill's own reference carry the corrected answer,
plus a symptom-table entry for a worker whose pane is dead seconds after spawn.
Verified live on an isolated instance (own data dir and tmux socket): fresh
case -> arrow at 5 s -> Enter at 7 s -> composer, with hasTrustDialogAccepted
recorded. With the server-side auto-accept disabled in a throwaway copy, the
skill's fallback cleared a genuinely parked dialog in 1.1 s and spawn_worker
took a brand-new case to a live composer in 7.2 s; spawn_workers + sendwait +
last_text then ran end to end.
Two prose lists in skills/codeman/ named some but not all external CLI
modes after the omp-mode rebase, which is exactly the drift
test/agent-skill-mode-lists.test.ts exists to catch: SKILL.md's
no-hook-signals list was missing omp, and endpoints.md's version-probe
sentence named pi/grok/omp as a bare 3-mode run with no matching class.
The agent skill could spawn a worker in any mode, but it could only
DRIVE a claude one: every other CLI has neither a real end-of-turn
signal nor an answer to read, so the recipes route them through output
markers.
dsh has both halves now -- its harness reports idle/working/blocked to
Codeman, and the previous commit reads its transcript -- so it joins
claude as a mode the four verbs work on unchanged. `spawn_workers alpha
beta:deepseek` is a mixed fleet in one call, and `sendwait` / `last_text`
/ `delete_session` need no per-mode variant.
Preamble 1.20.0 (SKILL.md's §0 heredoc regenerated from it):
- `spawn_worker` grows a deepseek branch that gates on the harness
composer. ⚠️ Readiness there is NOT the stop signal: the harness
reports idle at BOOT ~300 ms before its composer paints (measured
2.26 s vs 2.56 s after spawn), so a send-and-wait fired straight after
quick-start resolves on the boot edge, reports a turn that never ran,
and strands the prompt in a pane not yet taking input. Waiting for the
composer also spends that edge, since signals are edge-triggered.
- `spawn_workers` takes `name[:mode]`, so a mixed fleet stays one
concurrent call. Case names still have to be unique -- the mode never
disambiguates two workers that would share a directory.
- `sendwait` asks for `wait:"stop,exit"` instead of the `wait:true`
default set. That set also carries `idle`, which for an external CLI is
inferred from output stabilization: on a dsh worker whose TUI repaints
rarely, the re-wait resolved in 0 ms with `signal:"idle"` on a turn
with three minutes left to run. It also makes a wrong mode loud -- the
modes that cannot deliver `stop` answer 400 before writing anything,
instead of resolving on a flap.
- The self-heal resend carries `delivered:true` forward. The resend is a
tagged duplicate, so the server truthfully reports `delivered:false`
about a write it skipped, and §1's cleanup then read a completed turn
as an undelivered one and kept a finished worker forever.
- dsh workers spawn with the permission posture the Run button sends,
because the harness default still asks and a worker parked on an
approval row cannot finish a fan-out. The multi-user clamp still
applies.
Docs: a worked dsh flow in recipes.md, readiness and the signal rules in
verbs.md, and the corrections this makes necessary -- `stop`/`blocked`
are no longer claude-only, and `last-response` is no longer permanently
empty for deepseek. The integration guide gains a section on reading a
session back and driving one as a worker; its web-UI section was also
stale (that server moved out of a shell session).
The static guard that keeps those lists from naming some external CLIs but
not others is extended rather than exempted: it now knows the three real
classes inside that family (no transcript, no hook signals, and the
positive twin -- the modes whose answers can be read), with the hook class
derived from `hooksAvailableForMode()` so the predicate and the prose
cannot drift apart. Any other partial list still fails, and a new backend
belongs to none of the classes until someone says so.
endpoints.md named 1.18.x as the version where workspace hooks became a
setting, but 1.18.x servers do NOT have this behavior — an agent driving
one would falsely conclude its workspace has hooks. The feature ships in
1.19.0. And preamble.sh changed content this PR without bumping its
CODEMAN_PREAMBLE stamp, so a cache stamped 1.18.3 would pass the
staleness check while holding old bytes; stamp bumped to 1.19.0 in
preamble.sh and the SKILL.md heredoc together (byte-identity pin).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workspace-hooks install makes the skill's central hooks rule wrong in the
cautious direction. Six places told a worker that a linked case or a raw
workingDir has no `stop`/`blocked` and that send-and-wait cannot be trusted
there, so an agent would hand-roll output-marker synchronization in exactly the
workspaces where `wait:true` now works.
Rewritten against the setting rather than directory provenance:
- verbs.md §5.1: the where-to-spawn table, the rule paragraph (now naming
`workspaceHooksEnabled`, default ON, the add-only merge, and the boot sweep of
recovered sessions), and the silent-failure warning. The three cases that stay
hook-less regardless are called out: remote SSH sessions, docker cases that
opted out, and a workspace Codeman cannot write to.
- verbs.md §5.3: the send-and-wait precondition is "the workspace has the hooks
block", not "a case Codeman created".
- endpoints.md: the Signals-by-mode table is now keyed on the setting, with rows
for OFF, for remote/docker-opt-out, and for a session from an older server.
The old create-path grep list becomes a "before 1.18.x" note.
- SKILL.md §2 + the cost list, recipes.md Flow-1 contrast, messaging.md step 1.
"Check, do not assume" is kept and promoted to the load-bearing habit, because
the setting is not visible from the call and a session created by an older server
that has not restarted still has nothing.
The `spawn_worker` hooks grep STAYS: it guards the setting being off, remote
sessions, and older servers. Only its diagnostic changes, since "pick an unused
name" is no longer the fix. That text lives in both the §0 heredoc and
`preamble.sh`, which `test/agent-skill.test.ts` pins byte-identical, so both are
patched with the same bytes.
Docs only, no behavior change. 23 skill tests green, full test:ci 5109 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- SKILL.md: forbid the standalone preamble check and pre-spawn recon turns
(measured: two wasted model turns cost ~12s of a 28s two-worker run; the
hardened flow measured 20.2s cold / 12.8s warm end to end)
- Lineage lines: dip now hangs from the strip's bottom edge (cap 104 -> 64,
no stacked row offsets), fixing the deep bow in wrapped strips and keeping
row-1 arcs off row-2 tab labels; per-child color palette (skin blue first,
then matrix green, pink, violet, red, turquoise, orange) via an inline
--lineage-color custom property
- Session Options -> Session: per-TAB pop-out (open-in-window) button override
on top of the general showTabDetachButton setting; per-device localStorage
map rendered as the tab-show-detach class
- Tab alerts: seed the pending-hook state machine from GET /api/approvals
regardless of the approvals-inbox setting (reloads used to lose the red tab
entirely with the inbox off), clear unconditionally on approval_resolved,
and repaint the alert as a steady red/yellow ring + glow + status dot on a
::before overlay so it stays visible on the selected (active) tab until the
permission is actually resolved
- docs: worker warm-pool design sketch (verified numbers baked in)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two live failures from one root cause: Claude Code loads a same-named
user-level skill (~/.claude/skills/codeman, written once by `codeman skill
install`) over the fresh per-case copy, and nothing ever refreshed it. A
stale Aug-9 copy (pre fast-path, pre lineage header) made every agent-driven
spawn run the old recipes: workers spawned serially with pid polls and
without X-Codeman-Parent-Session, so the web UI drew no lineage arcs.
- refreshUserAgentSkill(): session create now refreshes a marker-owned
user-level copy (refresh-only: absent copies are not installed,
foreign/symlink copies stay untouched).
- seedAgentSessionPreamble(): local claude session create pre-seeds the
skill's preamble into ${XDG_CACHE_HOME:-~/.cache}/codeman-agent-<id>.sh,
single-sourced from the new skills/codeman/preamble.sh, so the skill's §0
bootstrap collapses to a two-line loader instead of a ~150-line paste the
model has to type out (measured ~47s of generation per run).
- SKILL.md: §0 now leads with the loader and keeps the full block as the
stale/missing fallback; explicit verbatim-paste warning (a hand-assembled
preamble is how the header and the fast-path functions got lost);
spawn_worker also sends parentSessionId in the body as defense in depth;
preamble stamp bumped to 1.18.3 so pre-fix cached preambles self-heal.
- test/agent-skill.test.ts pins preamble.sh byte-identical to the SKILL.md
heredoc and covers seeding (XDG + HOME fallback, 0600) and the user-level
refresh (absent/stale/foreign).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The changeset ships this as 1.18.2, so the stamp, the bootstrap's grep/write
condition, both re-source guards and the recipes guard all carry 1.18.2 now
instead of a version that would never exist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>