Skip to content

Releases: Ark0N/Codeman

codeman@1.15.0

Choose a tag to compare

@github-actions github-actions released this 09 Aug 10:39

Zero-lag predictive echo for Codex sessions

Zero-lag predictive echo for Codex sessions (mosh-style write-through prediction).

Codex's per-keystroke composer forced 1.12.2 to disable the local-echo overlay (issues #218/#219/#220/#222), leaving Codex typing at full round-trip latency on remote links. This release adds a second echo mode instead of re-enabling the first: every keystroke still goes to the PTY exactly as before (byte-identical wire behavior, pinned by vm-level and end-to-end trace-equality tests), while the new PredictiveEchoAddon in xterm-zerolag-input 0.2.0 paints the predicted glyph at the predicted cell. When the real echo lands, the prediction is confirmed and its span removed (an invisible swap); mispredictions self-heal via a two-pass mismatch cascade and a TTL.

  • Reconciliation reads the parsed terminal buffer, never the raw stream: full-line redraws, ECH gap painting and tmux's in-place deltas all converge to the same cells. Confirmation requires the cell match PLUS a cursor advance, so placeholder glyphs and identical repaints never false-confirm; blank cells are neutral (codex clears its placeholder on the first echo).
  • Predictions paint only while the cursor sits on the measured Codex composer row (/^› /, codex-cli 0.147): trust/approval modals and wrapped continuation rows get no ghosts, deliberately falling back to real echo.
  • Ships as a SEPARATE vendor/xterm-predictive-echo.js bundle: the existing zerolag bundle is byte-identical (sha256-verified), and a missing or broken bundle degrades Codex to exact 1.12.2 behavior. The per-device localEchoEnabled toggle is the kill switch.
  • Claude/Gemini/OpenCode/Antigravity keep buffer mode untouched; shell stays off.
  • A post-build adversarial review added the anchor-hold rule: after an unpredicted wire edit (backspace into echoed text, cleared input, IME text commits) new predictions hold until the next parsed write, so a stale displayed cursor can never mis-anchor a run.
  • Tests: 55 new package tests including replay suites driven by fixtures recorded from a real codex TUI through the production tmux+strip pipeline (scripts/dev/record-codex-frames.mjs) and a 500-iteration seeded fuzz; new vm policy/wire-neutrality suites; a 10-scenario Playwright E2E against real codex covering the #218/#219/#220/#222 retests, byte-identity, and a simulated 300ms-RTT run. The package test suite now runs in CI.

Agent-skill hardening, plus a fix for the mobile browser suite.

The Codeman agent skill

Twelve issues found by auditing the skill against a live instance, and fixing them meant measuring things rather than reasoning about them.

Readiness now works in every permission mode. The ladder matched bypass, which is the status bar of only ONE mode. Measured one pane per mode against claude-cli 2.1.226:

how Codeman spawned it statusline shift+tab bypass
--dangerously-skip-permissions (default) bypass permissions on yes yes
--permission-mode auto auto mode on yes no
--allowedTools … don't ask on yes no
neither (normal) don't ask on yes no
--permission-mode plan plan mode on yes no

Every mode ends (shift+tab to cycle), and the claudeMode setting is not exposed on GET /api/v1/sessions/:id, so there was nothing to branch on. The ladder matches shift+tab now: universal, and space-free, which is what makes it survive the TUI stream. A non-default worker used to be reported broken after burning the full budget. ⚠️ The + means it only works through --data-urlencode; a hand-built query silently searches for shift tab.

.status is documented as unreliable in both directions. Measured on a live worker reading idle while mid-turn and actively producing output, with lastActivityAt equal to the moment of the call. A worker that dies inside its pane also reads idle. Synchronize on stop or an output marker; to judge from outside, sample terminal?tail= twice and compare.

The self-delete guard is fail-closed. Documented in 1.14.2; the reference files and every recipe now route through it consistently.

Reads work on macOS. The ANSI-strip pipelines used sed 's/\x1b…', and BSD sed has no \xHH escape, so on macOS they silently stripped nothing and handed the agent raw ANSI.

Injection is atomic and no longer silent. installAgentSkillInto() wrote each file with a bare writeFile, so two sessions created concurrently in one repo could leave a reader observing a truncated SKILL.md; writes now go through temp+rename under the same lock every sibling mutator uses. And both server call sites discarded the outcome, so a foreign refusal (a user-authored skill is present) or a symlink refusal was invisible: turning the setting on, seeing nothing, and having no way to find out why. Refusals are logged now; injection stays best-effort and still cannot fail session creation.

Reference corrections: the FORBIDDEN 403 row and which auth responses are plain text rather than the JSON envelope, the input size cap, the undocumented killMux parameter on DELETE, and the fact that zero, negative and non-integer timeouts are rejected with a 400 rather than clamped.

README.zh-CN.md taught a recipe that could not work: its input example had no trailing \r, so Enter was never sent and the prompt sat unsubmitted, and its read step used /output, whose textOutput is always empty for interactive sessions. Its agent section is now in line with the English one. CLAUDE.md's single-line gotcha also gained the \r rule.

Tests: the codeman skill install/uninstall CLI had none, including the linked-case resolution shipped in 1.14.2; the POST /api/sessions injection call site was never exercised because the shared route mock hardcoded the gate off; and nothing guarded reference/endpoints.md against drifting from the routes it documents. All three covered now.

Mobile browser suite

The suite drives a real browser against a server started from TypeScript source, so it serves src/web/public, while npm run build puts the xterm vendor bundles in dist/web/public. Without them every /vendor/xterm* request 404s, Terminal is never defined, and every test touching app.terminal dies on a null. A pretest:mobile step now prepares them.

Hardened after two review rounds, each defect reproduced: the freshness cache trusted mtime alone, so a bundle left without its alias tail (or truncated by an interrupted npm install) was reported "up to date" forever while the suite died on LocalEchoOverlay is not defined; it now verifies content and size, and repairs what an earlier run poisoned. Builds go to a temp file private to the run and rename into place, so a partial write can never be published and two concurrent runs cannot corrupt each other. Temps whose owning process is gone are reclaimed, and only those. Freshness tracks every input the bundle derives from, not just the entry, so editing a sibling of the addon no longer leaves the suite testing a stale overlay. npx runs with the repo as cwd, so it uses the pinned esbuild instead of fetching an unpinned one.

Thanks

  • @Lint111 for #241, which found that the mobile browser suite could never have worked: it serves from source while the vendor bundles are only built into dist, so every terminal test was failing on a null. That had been invisible because CI excludes the suite.
  • @DodgyBadger and @mtiller, whose 1.14.2 reports (#237, #238, #232, #230) prompted the audit that turned up most of the agent-skill issues fixed here.

codeman@1.14.2

Choose a tag to compare

@github-actions github-actions released this 09 Aug 02:19

Four reported bugs fixed, and the Codeman agent skill from 1.14.1 gets its first published build with the fixes below alongside it.

The Codeman agent skill

Introduced in 1.14.1 and the headline of this line. skills/codeman is a Claude Code skill that lets an agent running inside a Codeman session drive the HTTP API: start worker sessions, send them prompts, block until they finish, read their answers and clean up. It ships in the npm package and self-gates, so outside a Codeman session (CODEMAN_MUX unset) it refuses to act and costs unrelated sessions nothing.

Installing it

codeman skill install                  # ~/.claude/skills/codeman, every new Claude Code session sees it
codeman skill install --case myproject # just that case; linked cases resolve by name too
codeman skill uninstall                # reverses either one

Or turn on App Settings > Agent Skill (agentSkillEnabled, synced, default off) and Codeman injects the skill into each case when a Claude session is created there.

Installs are marker-owned: a skills/codeman that Codeman did not write is never touched, a stale managed copy is refreshed in place, and a symlinked skill directory is refused rather than written through. Re-run codeman skill install after upgrading to refresh the copy. Turning agentSkillEnabled back off does not remove already-injected copies, because a create-time sweep would yank the skill out from under other live sessions sharing that .claude/ directory; remove them per case with codeman skill uninstall --case <name>.

Using it

Ask for orchestration in plain language ("spin up three workers, have them lint, typecheck and test in parallel, then report back") and the skill supplies the guard, the safety rules and the recipes. The flow it runs:

  1. Guard. Re-runs a preamble on every shell call that refuses outside CODEMAN_MUX=1, reads CODEMAN_API_URL and CODEMAN_SESSION_ID, recovers a password from the data dir .env or the install's service definition if one is set, and defines a fail-closed delete_session. It re-runs it every call because shell state does not survive between an agent's tool calls.
  2. Start a worker with POST /api/v1/quick-start (mode is any of claude, shell, opencode, codex, gemini, antigravity), checking .success before reading .data.sessionId.
  3. Wait until it is really ready. A new session reports idle before its CLI has spawned, and a brand-new case shows a trust dialog first, so the skill waits for the composer's own status bar and treats the dialog as a bounded fallback.
  4. Send and wait in one call: wait/waitTimeout on POST /api/v1/sessions/:id/input. It registers the waiter before typing, closing the race where a separate wait reports the previous turn's idle state as this turn's answer. For claude workers it resolves on the stop hook, usually within seconds.
  5. Read the answer from GET /api/v1/sessions/:id/last-response, which returns clean transcript text rather than a screen scrape.
  6. Clean up with delete_session, for ids it created and nothing else.

Hook-less modes (shell and the external CLIs) have no stop signal and coarse lifecycle transitions, so the skill synchronizes those with a unique split marker and wait-output ... from=buffer. Worked fan-out flows, the per-mode signal table, error codes and the Docker/remote caveats live in the skill's reference/ files, loaded on demand.

The rules it encodes

Each of these silently wastes a run, which is why they are written down: every input must end with \r or Enter is never sent; input is single-line; a wait timeout is HTTP 200 with wait.timedOut, not an error; stop and blocked are claude-only; signals are edge-triggered with no history, so never fire-and-forget N prompts and then gather signal-waits one by one; a typed command echoes into the output stream, so markers must be split; a full-screen TUI stream is space-less, so match single tokens; and pid != null proves startup, not life, so wait?until=exit is the death check.

Bug fixes

  • Web tabs: long-running proxied requests were aborted after 30 seconds with no server log (#237). The proxy wrapped each upstream fetch in a 30s AbortSignal.timeout, which bounds the entire exchange rather than the wait for response headers, so a dashboard endpoint doing model inference and any actively streaming response both died at 30s as a generic unlogged 502 that read as an intermittent network error. The timeout now bounds time-to-headers only and is cleared the moment headers arrive, with the default raised to 300s (CODEMAN_WEBVIEW_TIMEOUT_MS). Header timeouts are logged with a sanitized identity (method plus origin plus path, never the query string, which can carry the dashboard's tokens). A browser that navigates away mid-request now aborts the upstream fetch, guarded by writableFinished so a completed response never triggers it. The WebSocket handshake keeps its own 30s budget via the new CODEMAN_WEBVIEW_WS_HANDSHAKE_TIMEOUT_MS, since a handshake is connection establishment and waiting minutes on one only delays the browser's reconnect logic.
  • Web tabs: sandbox incompatibility with cookie-authenticated reverse proxies documented (#238). docs/web-tabs.md now covers cookie auth in front of Codeman itself (Cloudflare Access and similar), where a sandboxed frame's asset and API requests carry no auth cookie, bounce to the login provider, and leave the embedded app apparently unstyled while trusted mode works. The Test button's result now states its own scope: it verifies server-to-upstream reachability, not how the page behaves in a sandboxed frame.
  • A described session tab now shows just the description (#232). A session named w2-foo-bar: some description rendered both halves, so the generated id ate the width the chosen part needed. The tab shows the description alone, the w<n>-<case> id moves to the tooltip and stays in the session settings modal, and aria-label deliberately keeps the full name so screen readers still get the id. Undescribed tabs are unchanged. Right-click a tab to rename it inline. This also fixed a re-render loop: the incremental update compared against the full name, which a described tab never matched, so those tabs re-rendered on every pass.
  • codeman status now probes the running server (#230). The command runs in its own fresh process and reported that process's always-stopped Ralph loop under a bare "Status:", which reads as "the server is down" while the service is running fine and agents are reachable. It now probes the real server (CODEMAN_API_URL, else https then http on the local port, overridable with --url) and reports reachability, version and live session state; any HTTP answer proves the server is up, including a 401 from a password-protected install. The Ralph loop keeps its own codeman ralph status. This complements codeman web --status from the daemon work: that answers "did I start a daemon", this answers "is a server running at all".

Thanks

  • @DodgyBadger for #237, a precise report with environment, repro and the "works outside the tab, intermittent through Codeman" framing that placed the bug immediately, and for #238 including the observation that a passing Test button was itself misleading.
  • @mtiller for #232 and #230. #232 also surfaced a second bug nobody had noticed: described tabs were re-rendering on every pass.

codeman@1.14.1

Choose a tag to compare

@github-actions github-actions released this 09 Aug 01:40

The Codeman agent skill is now installable, so an agent running inside a Codeman session can drive the API without you pasting docs into its prompt. Plus six fixes to the packaged skill, each found by running it live against a real instance.

What the skill is

skills/codeman is a Claude Code skill that teaches an agent inside a Codeman session how to start worker sessions, send them prompts, block until they finish, read their answers and clean up. It ships in the npm package. It self-gates: outside a Codeman session (CODEMAN_MUX unset) it refuses to act, so installing it globally costs unrelated sessions nothing.

Installing it

Three ways, pick one:

codeman skill install                  # ~/.claude/skills/codeman, every new Claude Code session sees it
codeman skill install --case myproject # just that case; linked cases resolve by name too
codeman skill uninstall                # reverses either one

Or turn on App Settings > Agent Skill (agentSkillEnabled, synced, default off) and Codeman injects the skill into each case when a Claude session is created there.

Installs are marker-owned: a skills/codeman that Codeman did not write is never touched, a stale managed copy is refreshed in place, and a symlinked skill directory is refused rather than written through. Re-run codeman skill install after upgrading Codeman to refresh the copy.

Turning agentSkillEnabled back off does not remove already-injected copies, because a create-time sweep would yank the skill out from under other live sessions sharing that .claude/ directory. Remove them per case with codeman skill uninstall --case <name>.

Using it

Once installed, just ask: "spin up three workers and have them lint, typecheck and test in parallel, then report back". The skill supplies the guard, the safety rules and the recipes. What it does under the hood:

1. Guard. Every Bash call re-runs a preamble that refuses outside CODEMAN_MUX=1, reads CODEMAN_API_URL and CODEMAN_SESSION_ID, recovers a password from the data dir .env or the install's service definition if one is set, and defines a fail-closed delete_session. It re-runs it every call because shell state does not survive between an agent's tool calls.

2. Start a worker.

Q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
  -d '{"caseName":"worker-1","mode":"claude"}')
SID=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$Q")

mode is any of claude, shell, opencode, codex, gemini, antigravity.

3. Wait until it is actually ready. A new session reports idle before its CLI has spawned, and a brand-new case shows a trust dialog first, so the skill waits for the composer's own status bar and treats the dialog as a bounded fallback.

4. Send a prompt and wait for the turn to end.

BODY=$(jq -n --arg p "$PROMPT" '{input:($p+"\r"),useMux:true,clientId:"codeman-agent-1",seq:1,wait:true,waitTimeout:60000}')
"${CURL[@]}" -X POST "$API/api/v1/sessions/$SID/input" -H 'Content-Type: application/json' --data-binary "$BODY"

Send-and-wait registers the waiter before typing, which closes the race where a separate wait reports the previous turn's idle state as this turn's answer. For claude workers it resolves on the stop hook, typically within seconds.

5. Read the answer.

"${CURL[@]}" "$API/api/v1/sessions/$SID/last-response" | jq -r '.data.text'

6. Clean up. delete_session "$SID", for ids you created and nothing else.

Hook-less modes (shell and the external CLIs) have no stop signal and coarse lifecycle transitions, so the skill synchronizes those with a unique split marker and wait-output ... from=buffer instead. Worked fan-out flows, the per-mode signal table, error codes and the Docker/remote caveats live in the skill's reference/ files, loaded on demand.

The rules that bite

The skill documents these because each one silently wastes a run:

  • Every input must end with \r or Enter is never sent and the text sits unsubmitted on the worker's prompt. delivered:true means "written to the pane", not "submitted".
  • Input is single-line. Newlines are stripped.
  • A wait timeout is HTTP 200 with wait.timedOut:true, not an error. Loop over short waits; timeouts clamp to [1s, 600s] and the applied value comes back as wait.timeoutMs.
  • stop and blocked are claude-only. Requesting them elsewhere is a 400.
  • Signals are edge-triggered with no history. One that fires while no waiter is registered is unobservable afterwards, so never fire-and-forget N prompts and then gather signal-waits worker by worker.
  • Your typed command echoes into the output stream, so a marker that appears verbatim in the input line matches before the command runs. Split it.
  • A full-screen TUI stream is space-less, so match a single space-free token, never a phrase.
  • pid != null proves startup, not life. A worker that dies inside its pane keeps status:"idle" and a pid. wait?until=exit is the death check.

Fixes to the packaged skill

  • The self-delete guard failed open. The old is_self "$SID" || curl -X DELETE ... shape meant an undefined is_self exited 127, the || branch fired, and the agent deleted its own session with the one guard bypassed. That is reachable because shell state does not survive between tool calls, so a partially re-pasted preamble was enough. The DELETE now lives inside a fail-closed delete_session, which also refuses an empty id and refuses when $SELF is unset or too short to prove the target is not the caller.
  • clientId was built from $$. The pid changes between tool calls, so the documented "resend the identical request" loop stopped being recognized as a duplicate and retyped the prompt, submitting the turn twice. It is a fixed literal now.
  • GET /api/v1/sessions/:id/last-response was undocumented. It returns the agent's final message as clean transcript text; the terminal scrape the skill previously recommended returns a wall of TUI repaint noise with the answer buried in it. It is now the documented read path for claude and codex, with the terminal buffer demoted to diagnosis and hook-less modes. Because the transcript flush lags the stop signal, the recipes poll it instead of reading once.
  • quick-start responses were never checked for .success. On failure .data.sessionId is absent, jq -r prints the string null, and the flow burned its full readiness budget against /api/v1/sessions/null before reporting jq noise instead of the cause.
  • codeman skill install --case <name> could not resolve a linked case. It hardcoded ~/codeman-cases/<name> while the server resolves through linked-cases.json first, so it failed with "Case not found" for a case the web UI handled fine.
  • Documentation corrections: SESSION_BUSY on quick-start is the 50-session cap rather than the waiter cap; caseName resolves linked cases, so a generic name can land a worker in a real repo; and the claim that a toggle-off sweep exists was wrong, so the per-case skill uninstall cleanup is now stated in both the README and the code.

Also in this release

  • Terminal: the wheel is no longer forwarded to codex, which ignores SGR mouse reports. Measured against codex-cli 0.147.0: codex never enables mouse tracking, runs an inline viewport and pushes its transcript into the terminal's own scrollback, so SGR wheel reports written to its pane changed nothing while 967 rows of healthy local scrollback sat unused. Codex tabs now use the local-scrollback path like shell/gemini/opencode, which is the same history the scrollbar drag already reached. Claude's wheel forwarding is untouched.

Thanks

  • @DodgyBadger for reporting the completely dead wheel in codex tabs (#227), and for the detail that the scrollbar drag still worked, which is what identified the wrong routing branch.

codeman@1.14.0

Choose a tag to compare

@github-actions github-actions released this 09 Aug 00:37

Minor Changes

  • Daemon mode and service install, plus subagent hook hardening and terminal/idle-checker fixes.

    New: run Codeman in the background without a terminal (#239, closes #231)

    • codeman web -d starts the server detached: it survives closing the shell, logs to ~/.codeman/web.log, records a pidfile, and only reports success after the server actually answers /api/status (a port clash or missing dependency can never read as a clean start). codeman web --status and codeman web --stop manage it; --stop verifies the pid still looks like a Codeman server before signalling, so a recycled pid is never SIGTERMed.
    • codeman service install / status / uninstall: installs a systemd user unit (Linux) or LaunchAgent (macOS) so the server comes back after reboots. The unit carries the installing shell's PATH (launchd's default PATH finds neither an nvm/Homebrew node nor tmux/claude), never contains CODEMAN_PASSWORD, and uses the same instance-scoped unit names as install.sh and the self-updater so no second copy can end up supervised.
    • Both refuse to start a second server on one data dir (pidfile check plus a live probe): two servers on the shared tmux socket would attach to each other's sessions.
    • Why -d exists at all: nohup does not protect a Node process, Node re-arms SIGHUP even when it inherits "ignore", so nohup codeman web & still dies on HUP. The detached relaunch (setsid) removes the controlling terminal instead.

    Subagent background-work hooks (#233, thanks @Lint111)

    • The background Bash rewake helper now also watches the top-level parent transcript when the hook fires inside a subagent: Claude records a subagent's Bash result in its own subagents/agent-*.jsonl but queues the completion in the lead session transcript, so subagents previously never woke. It can also inline a CODEMAN_RESULT_BEGIN/END marked report (up to 64 KiB) from the task output file into the wake feedback.
    • New SubagentStop guard: a subagent that still owns live Monitor or background Bash processes is kept working instead of publishing an intermediate progress line as its final report. Ownership is verified against live process descriptors on tasks/<id>.output, so stale transcript text alone never blocks, and the guard fails open on systems without /proc.
    • Existing cases self-heal to the new hooks on next launch.

    AI idle checker: stderr kept out of the verdict (#234, thanks @Lint111)

    The claude -p verdict command no longer merges stderr into the verdict file, where CLI warnings could turn a valid verdict into a parse error. On failures, the first 200 chars of stderr are attached to the diagnostic instead.

    Terminal: large final batches drain fully (#235, thanks @Lint111)

    A render-scheduling flag was cleared after the flush instead of before it, so when a large batch left a remainder behind, the remainder stayed unrendered until unrelated output arrived. This looked like truncated responses or shell commands that never finish. The flush now reschedules itself until the queue is empty.

    Docs and tests

    • README documents daemon mode and service install.
    • Unique test port for the daemon-control suite.

Thanks

Huge thanks to the people who made this release happen:

  • @Lint111 for three quality PRs: the SubagentStop guard + parent-transcript rewake (#233), keeping CLI stderr out of AI idle verdicts (#234), and the deferred terminal flush fix (#235)
  • @mtiller for requesting daemon mode (#231), which became codeman web -d and codeman service install (#239)

codeman@1.13.0

Choose a tag to compare

@github-actions github-actions released this 08 Aug 23:19

Minor Changes

  • Agent wait primitives, the Codeman agent skill, a fix for hooks dying silently on HTTPS installs, and the tab-strip UX improvements from the previous batch.

    Agent wait primitives (new API surface, the reason this is a minor). Three bounded long-polls let an agent driving Codeman from a shell block instead of poll:

    • GET /api/v1/sessions/:id/wait blocks until a lifecycle signal fires (until=stop,idle,working,blocked,exit, fresh=1 to require a new transition).
    • GET /api/v1/sessions/:id/wait-output blocks until a literal substring appears in the session's output (match=, nocase=, from=now|buffer; never regex, by design).
    • wait/waitTimeout on POST /api/v1/sessions/:id/input (send-and-wait) registers the waiter before typing, closing the race where a separate wait reports the previous turn's idle state as this turn's answer.

    Shared semantics: a timeout is HTTP 200 with wait.timedOut: true (callers loop over short waits; tunnels cut idle connections), timeouts are clamped to [1s, 600s] and echoed back as wait.timeoutMs, all three nest the result under data.wait, and status/limitPaused ride along. stop/blocked exist for claude mode only: requesting them explicitly elsewhere is a 400, the default set silently narrows and echoes what it waited on. Capacity caps (16 waiters per session, 128 process-wide) answer 409/429, waiter slots release on client hang-up, and shutdown resolves parked waiters instead of stranding them. Bounds are operator-tunable via CODEMAN_WAIT_* env vars.

    Reliability details that came out of three verification rounds: a worker that dies inside its tmux pane is now detected at the mux layer (pane-death probe, ~750ms cache, a 3s watcher for waits already parked), so a corpse answers exit instead of idle and send-and-wait rolls back its dedup seq when the write went nowhere; output matching normalizes charset-designation escapes (a stock bash prompt's ESC ( B no longer breaks match=tnode:) and holds back partial escapes at chunk boundaries, so matches straddling PTY chunks are found.

    Codeman agent skill (skills/codeman). A packaged skill that teaches an agent running inside a Codeman session to drive the API safely: guard preamble (refuses outside CODEMAN_MUX=1, resolves credentials from the data dir .env or the install's service definition), self-protection (is_self prefix check in both directions), readiness for claude workers (composer-first, trust dialog as bounded fallback), send-and-wait loops that cannot report a never-submitted prompt as success, marker-synchronized shell flows, fan-out patterns, and cleanup discipline. Ships in the npm package via the files entry.

    Hooks were dying silently on every HTTPS install (bug fix). The generated hook curls lacked -k, so on --https installs (self-signed cert) every hook event (stop, permission_prompt, elicitation_dialog, idle_prompt, teammate_idle, task_completed) failed TLS verification and the failure was swallowed, taking respawn's definitive idle signals with it. Hooks are now generated with curl -sk, and a staleness detector regenerates the on-disk hook config of already-created cases the next time a session starts in them. Relatedly, CODEMAN_API_URL is no longer exported with a guessed http://localhost:3000 fallback (wrong scheme on HTTPS installs); it is omitted unless the server has stamped the real URL, so in-session guards fail closed.

    Tab strip (from the previous batch, reported by christianhaberl): action icons (kill/pop-out) now appear on the active tab only, middle-click closes a tab, tab hover uses a fixed width with a sliding title instead of resizing the strip, and the pop-out button is opt-in (default off).

    Docs. docs/api-reference.md gained the full long-polling contract (signals by mode, readiness, what the matcher sees, response discriminators); docs/extending-codeman.md and the README carry verified copy-paste orchestration recipes; docs/architecture-invariants.md records the load-bearing ordering, liveness, and edge-triggered-signal invariants. Net +163 tests (4300 passing in the CI sweep).

codeman@1.12.2

Choose a tag to compare

@github-actions github-actions released this 08 Aug 11:49

Patch Changes

  • Codex input fixes: all four bugs reported by @DodgyBadger traced to one root cause (the zero-lag local-echo overlay buffering keystrokes until Enter, which starves codex's per-keystroke composer) and fixed in terminal-ui.js:

    • Slash command picker never appeared in codex sessions (#222): the "/" sat in the overlay until Enter, so codex never saw it. Codex-mode sessions now use plain PTY echo (same branch as shell), so the picker pops and live-filters as you type.
    • Arrow keys dead while typing, backspace dead after Ctrl+Backspace (#218): arrows were forwarded to a still-empty composer while typed text sat pending, and after a control-char flush the overlay swallowed every backspace. Codex bypasses the overlay entirely now; the shared overlay branch (claude/gemini/opencode) additionally flushes pending text on composer nav keys, then hands the session to pass-through until Enter/Ctrl+C, and forwards backspace instead of swallowing it when the overlay has no state.
    • Pasting displaced the typed prompt (#219): bracketed pastes (xterm terminal.paste with DECSET 2004 active) were forwarded without flushing pending typed text, so the paste landed first. The shared branch now flushes typed text first and delays the paste sequence by 80ms, because codex's paste-burst handling drops keystrokes that arrive in the same PTY read as a bracketed paste (verified against codex 0.147.0 at the byte level).
    • Long prompts overflowed the bottom of the screen (#220): long typed prompts existed only in the overlay DOM so codex never grew its composer; with plain PTY echo the composer grows and rewraps normally.

    Verified end to end against a real codex 0.147.0 TUI driven by a headless browser: the pre-fix build reproduces all four bugs, the fixed build passes 17/17 assertions. New CI test file test/local-echo-codex-gating.test.ts (41 tests) pins the nav-key classifier, per-mode overlay gating, the flush helper, and pass-through routing. Known upstream limitation: Ctrl+Backspace deletes one character, not a word (xterm.js sends 0x08; word-delete needs kitty CSI-u encoding that xterm.js 6.0.0 cannot emit).

    Mobile keyboard viewport settling fixes by @Lint111 (#229): coalesce keyboard viewport settling so rapid visualViewport resize events during keyboard show/hide no longer thrash the terminal fit, and only arm the settle logic on a real keyboard transition instead of every viewport resize.

Thanks

codeman@1.12.1

Choose a tag to compare

@github-actions github-actions released this 07 Aug 23:39

Patch Changes

  • Terminal scrollback fixes, round 2 of issue #205. A Claude pane's local buffer is hollow (tmux keeps no history for a repaint-mode pane), and both retest reports traced back to that fact. The scroll-to-top full-history re-pull now refuses to rewrite the terminal when the capture holds less than the browser already does, so it can no longer delete history mid-scroll on iPhone (a refused session also re-fetches far less often). When wheel-forwarding is unavailable on a Claude session (version probe failed, CLI older than 2.1.187, or the "Wheel Scrolls Local History" opt-out) and there is no local scrollback to scroll, wheel and touch now page the CLI's own transcript via coalesced PageUp/PageDown instead of doing nothing. The claude --version probe no longer caches a failed run for the server's lifetime (one timed-out probe used to silently disable wheel-forwarding on every device until restart); failures retry with backoff. Every scroll gesture now logs a one-line [scroll] routing decision to the browser console for direct diagnosis, and the opt-out setting's tooltip explains that the paging fallback is Claude-only (Codex has none).

  • 2e69e28: Bound the process-tree walk that could take a machine down.

    getChildPids ran pgrep -P <pid> per node and recursed with no visited set, no
    depth limit and no node cap. Across ~28 adopted tmux trees the fan-out exploded,
    and because each pgrep blocks in the kernel while reading /proc/<pid>/cgroup
    under WSL, none returned while the walk kept spawning more — ~13,000 pgrep
    processes stuck in D-state out of ~39,000 total, load average above 13,000,
    recoverable only by restarting WSL.

    Now: one ps snapshot, breadth-first with a visited set, a depth cap and a node
    cap, in a pure module (proc-tree.ts) that the regression tests exercise
    directly. The snapshot is refreshed asynchronously, and the kill path forces a
    fresh one so the SIGKILL escalation cannot re-read pre-SIGTERM state.

  • ebfcac6: An input whose delivery fails can be retried instead of being lost for good.

    Both input paths recorded the (clientId, seq) pair as applied and acknowledged
    the frame before knowing whether the write had landed — the POST route because
    its mux write is fire-and-forget, the WebSocket handler because it ACKed
    unconditionally. When the write then failed, the client dropped the frame from its
    durable queue and the server rejected the retry as a duplicate: the reliable
    delivery layer was guaranteeing exactly-once delivery of something that had never
    been delivered.

    The bookkeeping is now rolled back on failure and the WebSocket ACK withheld, so
    the client redelivers. Session.write() reports whether it reached a PTY at all
    instead of silently swallowing the data.

    Response codes are unchanged: a session can legitimately have no PTY yet (created
    but not started), so turning that into a failure status would be a contract change
    of its own.

    Note this does not remove the root cause: the POST still answers 200 before the
    mux write is attempted, so a client that treats any 2xx as final still cannot
    learn about that failure. Closing that would mean awaiting the tmux child in the
    request path.

  • 1a32e63: Routes that answer with reply.raw.writeHead() no longer drop the headers the
    security hook set.

    writeHead writes straight to the Node response and bypasses Fastify's header
    store, so everything the onRequest hook granted was silently lost — including the
    Access-Control-Allow-Origin it emits for localhost origins, and the
    X-Content-Type-Options / X-Frame-Options / CSP headers. A localhost page could
    therefore call every other /api endpoint cross-origin while its EventSource
    failed CORS.

    Affects GET /api/events and the three raw-writing routes in file-routes.ts
    (file-raw, tail-file, download).

Thanks

  • @christianhaberl for three sharp fixes in one batch: the bounded process-tree walk (#225), retryable input delivery (#226), and the security headers on raw writeHead routes (#224).
  • @mtiller for the quick 1.12.0 retest on #205, and especially for the Fn+Up observation that cracked the scrollback diagnosis open.
  • @jonocodes for reporting #205 in the first place.

codeman@1.12.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 11:45

Minor Changes

  • Terminal scrollback overhaul (issue #205), fixing every reported scroll failure across shell and CLI sessions, desktop and mobile:

    • Shell, OpenCode and Antigravity sessions finally have working scrollback: tmux's own client-side alternate-screen switch is stripped for tmux-backed sessions (narrow strip: alt-screen toggles only, keeping clear's 3J and mouse DECSETs), so xterm stays in the normal buffer instead of a scrollback-less alt buffer where the wheel turned into shell history cycling and touch scrolling did nothing. Direct-PTY fallback sessions are untouched so fullscreen apps (vim/less/htop) keep the alt screen there.
    • The wheel listener now runs in capture phase and owns the scroll: xterm's internal vscode-style viewport scroller consumed wheel events whenever local scrollback existed (and goes deaf entirely after a tab switch or replay resets the terminal), which silently killed wheel forwarding, made scrolling break after reload/tab switches, and let the CLI's input box scroll away. Local scrolling goes through buffer-level scrollLines and keeps working after resets; mouse-tracking apps and alternate-buffer sessions are passed through untouched.
    • Wheel AND touch scrolling now forward to the CLI's own transcript for Codex and Claude 2.1.187+, at any scroll position (the viewport snaps home first), so the input box stays pinned on desktop and phones alike. Shift+wheel and the "Wheel scrolls local history" setting still pin local scrollback.
    • Smooth scrolling: local wheel scrolling glides with an ease-out animation (fractional line accumulation, so slow trackpad drags track the finger instead of running ahead).
    • Full tmux history on demand: the full-scrollback replay is now per session instead of once per page load, and scrolling up at the top of the buffer re-pulls the complete tmux history, recovering everything tmux's repaint bursts or tab switches removed from the browser's copy.
    • Firefox wheel speed: wheel deltas are normalized by deltaMode (Firefox reports line units, previously read as pixels and slowed ~4x).
    • Remote SSH Claude sessions now probe the CLI version over ssh (same connection options and login-shell wrapper as the real launch), so wheel forwarding works for them too instead of silently staying off.

    Docs: scrollback analysis and fix plan recorded in docs/, architecture invariants updated (strip flavors, capture-phase wheel ownership, per-session full-history replay); docker agent-image rebuild warning and integration-guide link fixes from the preceding docs commits.

codeman@1.11.2

Choose a tag to compare

@github-actions github-actions released this 06 Aug 05:32

Patch Changes

  • Make Antigravity (agy) a first-class CLI everywhere, and stop presenting Gemini CLI as a consumer product now that it is enterprise-only.

    Antigravity was already wired into the session layer, schemas, run-mode menu and remote/Docker command maps, but the surfaces around it were never updated. Gemini keeps full support; Antigravity now sits beside it.

    Fixes:

    • Docker cases with mode: 'antigravity' were broken. docker/agent.Dockerfile installs its CLIs from npm, and agy is not an npm package, so the binary was never in the image and the container died on command-not-found. It now gets its own installer step. The --dir /usr/local/bin flag is load-bearing: the installer's default $HOME/.local/bin resolves to root's home at build time and would be unreachable by the agent user the container runs as. Note the binary is roughly 190MB, making it the largest layer in the image, so rebuild with node scripts/build-agent-image.mjs when convenient.
    • Welcome screen gained a "Run Antigravity" action, gated on agy being present like the other CLI buttons, styled with the same cyan identity as the toolbar run button and run-mode dot.
    • install.sh now detects agy (search paths mirroring antigravity-cli-resolver.ts), counts it as a satisfying AI CLI so an Antigravity-only box is not told it has none, and recommends it instead of Gemini in the install hints.

    Documentation corrections where it had become factually wrong: architecture-invariants.md described isExternalCliMode() as opencode/codex/gemini when the code has included antigravity for some time, said "all three modes", and omitted ANTIGRAVITY_* from the env-prefix allowlist row; the agentType enum in cron-guide.md, SessionMode in cron-discovery.md, and RemoteCommandMode in remote-sessions.md were all stale.

    Also updated both READMEs (five CLIs, Gemini marked enterprise-only), the antigravity npm keyword, and comment drift in eight places. Test coverage added for the new welcome button.

    Antigravity stores its state under ~/.gemini/antigravity-cli/ rather than a ~/.antigravity directory, so the existing .gemini Docker credential seed already covers it. That is now recorded in a code comment so no dead configuration gets added later.

  • b982c5d: Keep the brief Response Viewer output inside the same message card and Markdown wrapper used by the full conversation view, so opening the viewer without clicking More preserves the same readable formatting.

codeman@1.11.1

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:38

Patch Changes

  • fix(history): Past Sessions data quality, and gate the phone run picker on CLI availability

    Past Sessions data quality (#215). Three bugs in the transcript scanner behind
    the Cmd+K Session Manager and the phone overview's PAST SESSIONS list:

    • Automated/SDK-driven transcripts (CI review bots and other tooling, which Claude
      Code stamps with a non-cli entrypoint) were listed alongside real interactive
      sessions even though they were never resumable. They are now excluded. Detection
      scans every entrypoint-bearing message rather than stopping at the first, so a
      transcript that began under an older Claude Code build and only later picked up a
      non-cli entrypoint is no longer wrongly hidden.
    • A resumed session could show a same-directory sibling's preview text as its own.
      The workingDir backfill in mergeUnifiedSessions() now only ever applies to rows
      that have no history entry of their own, so it can no longer overwrite a row's real
      content with another conversation's.
    • Sessions restarted many times accumulated enough bookkeeping lines to push the real
      first prompt past the scanner's 16KB head-read window, leaving a blank row. The read
      is now two-tier: 16KB first, escalating to 128KB only when that was not enough, which
      is both correct and cheaper than reading 128KB unconditionally (measured on a real
      transcript tree: 36% fewer bytes read, roughly 17.5% faster than the unconditional
      version). Also restores the tail-read fallback for a file whose head read failed
      outright (for example EMFILE while scanning hundreds of files), which had been
      silently dropping the session from history.

    Follow-up hardening on top of the above: the automated-transcript exclusion now
    blocklists the SDK entrypoint shape (sdk, sdk-cli, sdk-py) instead of allowlisting
    the exact value cli. Because the check hides rows, an allowlist failed closed on any
    value Claude Code has not shipped yet: a future rename of the interactive entrypoint,
    or a second interactive host, would have blanked the entire Past Sessions list with
    nothing in the UI to explain it. An unrecognized automated entrypoint now costs a few
    noisy rows instead, which is the annoyance this filter set out to fix rather than a
    broken feature.

    Phone overview run picker (#214). The "C" logo home screen's Run picker listed all
    six backends regardless of what was installed, so tapping an uninstalled one produced a
    failed launch instead of the entry simply not being offered. It is now gated on
    isCliAvailable() exactly like the desktop toolbar's run-mode dropdown (shell exempt,
    since it has no external CLI dependency and keeps the menu from ever being empty). The
    picker is a hardcoded duplicate of the toolbar menu rather than a shared render, which
    is why it never picked up the earlier gating work; a test now asserts that every mode
    the picker offers is gated, so a newly added backend cannot silently drift again.

  • 73315bc: fix(web): stop the Claude response viewer from following another session's conversation

    The viewer re-derived a pane's live conversation by taking the newest
    ~/.claude/history.jsonl entry for the pane's cwd. A cwd is shared with every
    other Codeman tab on it, with tabs long since closed, and with any plain
    claude run in the user's own terminal, so the eye followed whichever of those
    was typed into last — and the adoption was written back to the session, so the
    mispin persisted. Entries are now credited to a pane only when they land within
    10s of that pane's own Enter and no other pane on the cwd submitted closer, the
    same last-submit correlation the Codex locator already uses.

    That correlation also has to survive a restart. start() resets
    claudeSessionId to the launch id even when re-attaching to a mux session whose
    CLI has since moved on via /clear, so a recovered pane pointed the viewer at
    its pre-/clear transcript — and with the anchor itself living only in memory,
    nothing corrected it until the user happened to type again. lastSubmitAt is
    now persisted in SessionState and restored on boot recovery, so the viewer
    re-derives the live conversation on its first poll.