---
name: Codeman
slug: codeman
category: Automation
description: Codeman drives the session manager’s HTTP API to list sessions, start workers, send prompts, wait for output, read results, and clean up. Use it to orchestrate parallel work or watch other Codeman sessions from inside a managed session.
github: "https://github.com/Ark0N/Codeman/tree/master/skills/codeman"
language: TypeScript
stars: 714
forks: 98
install: "npx degit https://github.com/Ark0N/Codeman/tree/master/skills/codeman ~/.claude/skills/codeman"
installs_to: ~/.claude/skills/codeman
source_path: skills/codeman/SKILL.md
collection_size: 1
category_size: 1523
added: 2026-08-23T05:20:19.485Z
last_synced: 2026-08-23T05:20:19.485Z
canonical_url: "https://dirskills.com/skills/codeman"
---

# Codeman

Codeman drives the session manager’s HTTP API to list sessions, start workers, send prompts, wait for output, read results, and clean up. Use it to orchestrate parallel work or watch other Codeman sessions from inside a managed session.

**Install:**

```bash
npx degit https://github.com/Ark0N/Codeman/tree/master/skills/codeman ~/.claude/skills/codeman
```

## README

# Driving Codeman from inside a session

You are an agent running inside a Codeman-managed terminal session. Codeman is the
server that spawned you; its HTTP API can start, prompt, watch, and delete other
sessions.

**Read as far as your job needs and no further.** §0 is the bootstrap, run once. §1 is
the whole fast path: spawn N workers, task them, collect answers. **If §1 covers your
job, run it and stop there.** The sections after it are for jobs it does not cover, and
reading them to be thorough is the main reason a ten-second run takes minutes. §2 is the
verb table when your job is a different one. §3 and §4 are the rules; §6 is setup and
credentials, which you only need when something 401s.

Everything else loads on demand, and is meant to be opened at one section, not read
through: the verbs in detail (the old §5) in [reference/verbs.md](reference/verbs.md),
worked multi-worker flows in [reference/recipes.md](reference/recipes.md), endpoint
tables and a symptom gallery in [reference/endpoints.md](reference/endpoints.md), and
direct messaging to claude workers in [reference/messaging.md](reference/messaging.md).

## 0. Guard and bootstrap

If `CODEMAN_MUX` is not `1`, **stop and say so**. Do not guess an API URL; a server
you are not part of is not yours to drive.

⚠️ **Your shell state does not survive between tool calls.** Each Bash call starts a
fresh shell, so `$API`, `$SELF`, the `CURL` array and `delete_session` are all gone by
the next call, and `$$` is a different pid. **The filesystem does survive**, so write
the preamble to a file once and source it afterwards, rather than re-pasting a
hundred-odd lines at the top of every call (a half-re-pasted preamble used to be the
single most likely way to break a run).

**Codeman seeds the preamble file for you** when it spawns a claude session (server
1.18.3+), so the bootstrap is usually nothing at all: these are the two lines every
later call opens with, and your first REAL call performs them anyway:

```bash
. "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
[ "${CODEMAN_PREAMBLE:-}" = 1.19.0 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
```

⚠️ **Never spend a Bash call on this check alone.** §1's block opens with this same
loader, so when §1 is the job, start there: the check rides the spawn call for free,
and a standalone "preamble OK" call buys nothing while costing a full model turn
(measured live: a lone check plus the deliberation around it added ~6 s to a 28 s
two-worker run). §0 is done the moment any job call passes its opening check. Only
when a call reports missing or stale, run the full block below once — and run it
**verbatim**: paste it as-is, never re-type it, trim it, or "extract the parts you
need". A hand-assembled
preamble is the documented failure mode of this skill: one live run rebuilt it
"minimally" and lost the `X-Codeman-Parent-Session` header (every worker spawned with
no lineage arc in the web UI) and the fast-path functions (the spawn fell back to a
serial quick-start loop plus pid polls), turning a ten-second job into a fifty-second
one. If your harness directs temporary files into a scratchpad directory, that
directive covers task scratch, not this file: it is a per-session cache that every
later call re-sources by this exact path, so keep the path below. If you must relocate
it anyway, copy the block's content byte-for-byte unchanged and source your path in
every later call instead.

```bash
test "${CODEMAN_MUX:-}" = 1 || { echo "Not inside a Codeman-managed session; refusing to act."; exit 1; }
: "${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}" "${HOME:?HOME not set}"
PRE="${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh"
mkdir -p "$(dirname "$PRE")"
# Rewrite unless the file already ends with THIS version's stamp, so a stale or a
# half-written file self-heals here instead of costing you a round trip to rm it.
grep -qs '^CODEMAN_PREAMBLE=1.19.0$' "$PRE" || (umask 077; cat > "$PRE" <<'PREAMBLE'
# ---- Codeman agent preamble 1.19.0 (seeded by Codeman at session spawn; the SKILL.md §0 bootstrap rewrites it when missing or stale) ----
API="${CODEMAN_API_URL:?CODEMAN_API_URL not set; refusing to guess}"
SELF="${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}"
# Credentials, cheapest first. Your session has usually INHERITED the server's
# CODEMAN_PASSWORD already (§6 explains why, and what to do when it has not);
# the data dir's .env is the documented fallback, the same one `codeman attach`
# reads. The data dir is wherever the hook-secret file lives. Values may be
# quoted or `export`-prefixed.
ENV_FILE="${CODEMAN_HOOK_SECRET_FILE:+${CODEMAN_HOOK_SECRET_FILE%hook-secret}.env}"
envval() { sed -n "s/^\(export \)\{0,1\}$1=//p" "$ENV_FILE" | tail -1 | sed 's/^"\(.*\)"$/\1/; s/^'\''\(.*\)'\''$/\1/'; }
if [ -z "${CODEMAN_PASSWORD:-}" ] && [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
  CODEMAN_USERNAME=$(envval CODEMAN_USERNAME)
  CODEMAN_PASSWORD=$(envval CODEMAN_PASSWORD)
fi
AUTH=(); [ -n "${CODEMAN_PASSWORD:-}" ] && AUTH=(-u "${CODEMAN_USERNAME:-admin}:$CODEMAN_PASSWORD")
# -k: harmless on http, required on https (self-signed cert).
# X-Codeman-Parent-Session: tags workers YOU spawn as your children, so the web UI can
# draw the lineage. Set once here and every present and future create call carries it;
# it is ignored on every other endpoint. Purely cosmetic (see §5.1) and it can never
# fail a spawn, so there is no case where you would want to leave it off.
CURL=(curl -sk "${AUTH[@]}" -H "X-Codeman-Parent-Session: $SELF")
CID=codeman-agent-1            # FIXED literal, never "agent-$$": see below

# Fail-CLOSED session delete. The DELETE lives INSIDE the guard on purpose: the older
# `is_self "$SID" || curl -X DELETE ...` shape failed OPEN, because an undefined
# is_self exits 127 and the `||` branch then ran the delete completely unguarded.
# Undefined delete_session is "command not found", which deletes nothing.
delete_session() {
  local id="${1:-}"
  [ -n "$id" ] || { echo "refusing: empty session id"; return 1; }
  [ "${#SELF}" -ge 8 ] || { echo "refusing: \$SELF unset or too short to prove this is not me"; return 1; }
  # ids appear in full AND 8-char form (Docker exports a truncated $SELF; mux names and
  # UI surfaces carry 8-char ids), so compare by prefix in BOTH directions. Equality or
  # a one-directional check each miss a real combination, and the miss deletes you.
  case "$id" in "$SELF"*) echo "refusing: $id is me"; return 1 ;; esac
  case "$SELF" in "$id"*) echo "refusing: $id is me"; return 1 ;; esac
  "${CURL[@]}" -X DELETE "$API/api/v1/sessions/$id"
}

# ---- fast path: the four verbs, already written. §1 composes them. ----
_composer_up() {   # <sid> <timeoutMs> -> "true"/"false". `shift+tab` is the one token
  "${CURL[@]}" -G "$API/api/v1/sessions/$1/wait-output" \
    --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' \
    --data-urlencode "timeout=$2" | jq -r '.data.wait.matched // false'
}
# spawn_worker <caseName> [mode] -> session id on stdout, diagnostics on stderr.
# quick-start AND readiness in one call, with a strict contract: NON-EMPTY stdout means
# a READY claude worker in a hook-carrying case. Anything less is rc 1 with EMPTY
# stdout, and the half-spawned session is deleted here rather than handed back, because
# a worker that never drew its composer would eat the task prompt with its trust
# dialog. There is deliberately no pid poll: wait-output already blocks until the
# composer draws, and pid!=null proved startup, never readiness.
spawn_worker() {
  local name="${1:?spawn_worker needs a case name}" mode="${2:-claude}" q sid cp r
  # parentSessionId doubles the CURL header, so a spawn_worker copied off the shared
  # curl (or a body someone rebuilt from this recipe) still carries its lineage.
  q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
      -d "$(jq -nc --arg n "$name" --arg m "$mode" --arg p "$SELF" '{caseName:$n,mode:$m,parentSessionId:$p}')")
  sid=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$q")
  # NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
  [ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
  [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; }   # only claude draws a composer
  # The server installs hooks into every claude workspace now, so this grep normally
  # passes; it stays because the install is gated on a setting the operator can turn
  # off, remote sessions never get hooks, and a session created by an older server
  # still has none. No marker means sendwait would false-resolve on flapping idle,
  # possibly inside the user's REAL repo: refuse rather than run the job there.
  cp=$(jq -r '.data.casePath // empty' <<<"$q")
  grep -qs '/api/hook-event' "$cp/.claude/settings.local.json" || {
    echo "case '$name' resolved to '$cp', which has no Codeman hooks (workspaceHooksEnabled off, remote, or an older server?): turn the setting on, or work §5.1+§5.5 by hand with markers" >&2
    delete_session "$sid" >/dev/null; return 1; }
  # Short composer wait FIRST, then the trust-dialog probe: a case still showing the
  # dialog can never pass the composer wait, so probing early keeps a cold case from
  # paying the whole long wait before the fallback even runs (§5.2). A warm case
  # matches in under a second and never reaches the probe.
  r=$(_composer_up "$sid" 5000)
  if [ "$r" != true ]; then
    if "${CURL[@]}" -G "$API/api/v1/sessions/$sid/wait-output" \
         --data-urlencode 'match=trust' --data-urlencode 'from=buffer' --data-urlencode 'timeout=2000' \
       | jq -e '.data.wait.matched' >/dev/null; then
      # Codeman's own auto-accept gives up after 90 s / 3 tries; this is that bounded fallback.
      "${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" -H 'Content-Type: application/json' \
        -d "$(jq -nc --arg c "$CID-$sid" '{input:"\r",useMux:true,clientId:$c,seq:1}')" >/dev/null
    fi
    r=$(_composer_up "$sid" 45000)
  fi
  [ "$r" = true ] || { echo "worker $sid never drew a composer; deleted it. Retry by hand via the §5.2 ladder (its billed stage-4 probe included)" >&2
    delete_session "$sid" >/dev/null; return 1; }
  printf '%s\n' "$sid"
}
# spawn_workers <caseName>... -> one "<caseName> <sessionId>" line per worker, in order;
# the sessionId column is EMPTY for a spawn that failed (stderr has why). CONCURRENT:
# N workers cost about what one costs. Spawning them one Bash call at a time is the
# single biggest avoidable delay in this skill. Names must be UNIQUE: two workers in
# one case directory co-edit the same tree (§4), so a repeat is an error here, not a race.
spawn_workers() {
  local d n i=0
  [ "$#" -gt 0 ] || { echo "spawn_workers: no case names given" >&2; return 1; }
  [ -z "$(printf '%s\n' "$@" | sort | uniq -d)" ] || { echo "spawn_workers: duplicate case names" >&2; return 1; }
  d=$(mktemp -d "${TMPDIR:-/tmp}/codeman-spawn.XXXXXX") || return 1
  for n in "$@"; do ( spawn_worker "$n" > "$d/$i" ) & i=$((i+1)); done
  wait
  i=0; for n in "$@"; do printf '%s %s\n' "$n" "$(cat "$d/$i" 2>/dev/null)"; i=$((i+1)); done
  rm -rf "$d"
}
# sendwait <sid> <prompt> [seq] -> blocks until that worker's turn ENDS (~10 min ceiling
# across its two waits). One billed turn. The \r and the per-worker clientId are applied
# here, which is why you never hand-build this body. seq defaults to the CURRENT EPOCH
# SECOND so that every new prompt is a new frame: the server drops any (clientId,seq)
# pair it has already applied, so a fixed default would make every later prompt to that
# worker a silent no-op that still "succeeds" and reports the previous turn's state.
# Pass seq explicitly for exactly one reason: resending a possibly-delivered frame as a
# deliberate duplicate, at the SAME number (§5.3).
# Delivery is SELF-HEALING: an Ink repaint occasionally eats the Enter, leaving the
# typed prompt stranded on the composer while a long wait runs its whole timeout
# (observed live). So the first wait is short; on its timeout a bare \r goes out (the
# missing Enter when the prompt is stranded, a no-op when the turn is genuinely
# running), then the ORIGINAL frame is resent unchanged, which the server takes as a
# tagged duplicate: it re-waits without retyping (§5.3). Trustworthy only for a claude
# worker spawn_worker handed back (hooks vetted); hook-less workspaces and other modes
# resolve on flapping idle: markers instead (§5.5).
sendwait() {
  local sid="${1:?}" p="${2:?}" seq="${3:-$(date +%s)}" body r
  body=$(jq -nc --arg p "$p" --arg c "$CID-$sid" --argjson s "$seq" \
    '{input:($p+"\r"),useMux:true,clientId:$c,seq:$s,wait:true,waitTimeout:20000}')
  r=$("${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" \
        -H 'Content-Type: application/json' --data-binary "$body")
  if jq -e '.data.delivered and .data.wait.timedOut' <<<"$r" >/dev/null 2>&1; then
    "${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" -H 'Content-Type: application/json' \
      -d "$(jq -nc --arg c "$CID-$sid" --argjson s "$(date +%s)" \
        '{input:"\r",useMux:true,clientId:$c,seq:$s}')" >/dev/null
    r=$("${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" \
          -H 'Content-Type: application/json' --data-binary "$(jq -c '.waitTimeout=580000' <<<"$body")")
  fi
  printf '%s\n' "$r"
}
# last_text <sid> [prev] -> that worker's last assistant message. Polled, because the
# transcript write LAGS the stop signal, and "some text exists" is not "THIS turn's
# text exists": right after a SECOND turn on the same worker the endpoint still serves
# the previous answer for a beat (observed live). When reading consecutive turns, pass
# the previous answer as [prev]: the poll then holds out for text that differs from it,
# falling back to whatever it last saw if the budget runs dry, so an honestly repeated
# answer still comes back. Non-zero exit means the worker really never wrote one.
last_text() {
  local t="" prev="${2:-}"
  for _ in $(seq 1 15); do
    t=$("${CURL[@]}" "$API/api/v1/sessions/$1/last-response" | jq -r '.data.text // empty')
    [ -n "$t" ] && [ "$t" != "$prev" ] && { printf '%s\n' "$t"; return 0; }
    sleep 1
  done
  [ -n "$t" ] && { printf '%s\n' "$t"; return 0; }
  return 1
}

# The stamp is the LAST line on purpose (a truncated write leaves it unset) and is kept
# bare on purpose: the write condition above anchors on it with $, so an inline comment
# here would fail that match and rewrite this file on every single bootstrap.
CODEMAN_PREAMBLE=1.19.0
PREAMBLE
)
. "$PRE"; [ "${CODEMAN_PREAMBLE:-}" = 1.19.0 ] || { echo "preamble at $PRE is stale or truncated: rm it and re-run this block"; exit 1; }
```

Every later Bash call that touches the API starts with the same two loader lines from
the top of this section.

Why it is built this way, all of it load-bearing:

- **It still fails closed.** A missing or truncated file means `delete_session` is
  undefined, and an undefined function is "command not found", which deletes nothing.
  ⚠️ This argument covers accidents, NOT a hostile file: a *complete* attacker-written
  preamble can define `delete_session` and set the stamp, and sourcing executes it. What
  defends against that is the path choice in the next bullet, not this one. Never
  hand-roll a `DELETE` of your own, which is the one thing that would route around this.
- **The version stamp is the LAST line, and the write condition greps for it.** That one
  choice covers staleness and truncation together: an old skill version's file and a
  half-written one both fail the grep and are rewritten in place, so neither costs you a
  round trip to diagnose and `rm`. The older `[ -s "$PRE" ]` condition could not tell a
  complete file from a half-written one and left both to the post-source guard, which can
  only refuse, not repair. That guard stays as the fail-closed backstop: if the rewrite
  itself is cut short, `CODEMAN_PREAMBLE` is unset and the call stops.
- **Not `/tmp`.** On a shared machine `/tmp` is world-writable, so another local user
  can pre-create the exact path you are about to `.` and have their code run as you.
  `$HOME`-derived paths are not world-writable, and the file is written 0600 anyway.
  The file holds the credential-*recovery code*, not a recovered password.
- **Never put `$$` in a `clientId`.** It changes per call, so the "resend the identical
  request" loop in §5.3 would stop being a duplicate and would **retype the prompt**,
  submitting the turn twice. Use the fixed literal `$CID`.
- Only real environment variables (`CODEMAN_*`, `HOME`) survive, which is why the
  preamble rebuilds `$API` and `$SELF` from them on every source rather than baking
  them in.

If a call comes back as unparseable text instead of JSON, that is almost always a
plain-text 401: see §6 and [the symptom gallery](reference/endpoints.md#symptom-gallery).

## 1. The fast path: N workers, one Bash call

**If the job is "spawn N claude workers, give them tasks, collect the answers", this
block is the whole thing. Run it, report, and stop reading. §2 onward is for jobs this
does not cover; you are not being careless by not reading them.**

Fill in the case names and the prompts, then run it as your FIRST Bash call: no
standalone preamble check before it (line one below IS that check), and no
reconnaissance. `ls ~/codeman-cases` answers nothing this block needs: invented
fresh names need no lookup, and `spawn_worker` refuses a name that already exists
rather than silently reusing it. Everything below is `spawn_workers` / `sendwait` /
`last_text` / `delete_session` from the §0 preamble, so there is nothing to assemble
and no per-call body to hand-build.

```bash
. "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null   # §0 loader
[ "${CODEMAN_PREAMBLE:-}" = 1.19.0 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
N=(alpha beta)                    # INVENT one fresh case name per worker; never list cases first
T=('reply with one line: the absolute path of your working directory'
   'reply with one line: your model name')            # tasks, same order as N

S=(); while read -r _ s; do S+=("$s"); done < <(spawn_workers "${N[@]}")   # concurrent
for i in "${!N[@]}"; do [ -n "${S[$i]:-}" ] || FAIL=1; done
[ -z "${FAIL:-}" ] || { echo "a spawn failed (stderr says why; §5.1): deleting the siblings"
  for s in "${S[@]}"; do [ -n "$s" ] && delete_session "$s" >/dev/null; done; exit 1; }

D=$(mktemp -d) || { for s in "${S[@]}"; do delete_session "$s" >/dev/null; done; exit 1; }
for i in "${!N[@]}"; do sendwait "${S[$i]}" "${T[$i]}" > "$D/$i" & done; wait
for i in "${!N[@]}"; do
  jq -ce --arg n "${N[$i]}" \
    '{worker:$n,delivered:.data.delivered,timedOut:.data.wait.timedOut,signal:.data.wait.signal}' \
    "$D/$i" || echo "{\"worker\":\"${N[$i]}\",\"error\":\"send produced no result\"}"
  echo "== ${N[$i]}"; last_text "${S[$i]}" || echo "(no response written)"
done
for i in "${!N[@]}"; do   # delete ONLY what finished; a timeout means STILL WORKING (§3 rule 5)
  if jq -e '.success and .data.delivered and (.data.wait.timedOut|not)' "$D/$i" >/dev/null 2>&1
  then delete_session "${S[$i]}" >/dev/null
  else echo "kept ${N[$i]} (${S[$i]}): its line above says why; re-wait or repair (§5.3), then delete_session it"
  fi
done; rm -rf "$D"
```

Measured against a live 1.18.0 server: two cold workers spawned and ready in **6.3 s**,
both turns dispatched and both answers read in **4.0 s** more. If your run takes minutes,
the time went into deliberation, not the API. The four things that actually cost time:

- **Spawning serially.** One worker per Bash call is one model turn per worker. `&` plus
  `wait`, as above, makes N workers cost about what one costs.
- **Reconnaissance turns before the spawn.** A standalone preamble check, an
  `ls ~/codeman-cases`, a `list_sessions` "to see what is ther
