mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Merge pull request #305 from Ark0N/docs/skill-hooks-rule
docs(skill): hooks are a setting now, not who created the directory
This commit is contained in:
+16
-15
@@ -47,7 +47,7 @@ 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.18.3 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
|
||||
[ "${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
|
||||
@@ -75,8 +75,8 @@ 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.18.3$' "$PRE" || (umask 077; cat > "$PRE" <<'PREAMBLE'
|
||||
# ---- Codeman agent preamble 1.18.3 (seeded by Codeman at session spawn; the SKILL.md §0 bootstrap rewrites it when missing or stale) ----
|
||||
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
|
||||
@@ -138,14 +138,14 @@ spawn_worker() {
|
||||
# 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
|
||||
# quick-start RESOLVES the name before creating: a linked case or an existing dir
|
||||
# wins over a fresh scratch case, so "created => hooks" is only true after this one
|
||||
# local grep (the same marker the server itself checks for). No marker means sendwait
|
||||
# would false-resolve on flapping idle, possibly inside the user's REAL repo: refuse
|
||||
# rather than run the job there.
|
||||
# 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 (linked or pre-existing?): pick an unused name, or work §5.1+§5.5 by hand" >&2
|
||||
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
|
||||
@@ -233,10 +233,10 @@ last_text() {
|
||||
# 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.18.3
|
||||
CODEMAN_PREAMBLE=1.19.0
|
||||
PREAMBLE
|
||||
)
|
||||
. "$PRE"; [ "${CODEMAN_PREAMBLE:-}" = 1.18.3 ] || { echo "preamble at $PRE is stale or truncated: rm it and re-run this block"; exit 1; }
|
||||
. "$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
|
||||
@@ -287,7 +287,7 @@ 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.18.3 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
|
||||
[ "${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
|
||||
@@ -343,7 +343,8 @@ Four things this block leans on, each one link away, no detour needed to run it:
|
||||
`~/codeman-cases/<name>`, not your repo. A name that already means something (a
|
||||
linked case, a pre-existing directory) is refused by `spawn_worker` rather than
|
||||
silently reused. Spawning where the work actually is (a linked case, a git worktree)
|
||||
is a different call with **no hooks**, and the costliest mistake in this skill: §5.1.
|
||||
is a different call, and picking the wrong one is the costliest mistake in this
|
||||
skill: §5.1. Those workspaces do get hooks now, unless the operator disabled it.
|
||||
- `sendwait` supplies the `\r`, picks a fresh `seq`, and self-heals a stranded Enter.
|
||||
A prompt without the `\r` is never submitted (§3), a reused `seq` is silently
|
||||
swallowed as an already-applied duplicate, and an Enter eaten by an Ink repaint
|
||||
@@ -358,9 +359,9 @@ One row per job. Acting on this table alone is correct; the §5 links are the de
|
||||
|
||||
| I want to | Call | Detail |
|
||||
|-----------|------|--------|
|
||||
| start a worker **where the work is** | `POST /api/v1/quick-start {"caseName":…}`, which **creates** `~/codeman-cases/<name>` unless the name is already a case: full signals there. Any other path (a git worktree): `POST /api/v1/sessions {"workingDir":…}` then `POST /api/v1/sessions/:id/interactive`, and expect **no hooks**. N workers means N worktrees | [§5.1](reference/verbs.md#51-where-to-spawn) |
|
||||
| start a worker **where the work is** | `POST /api/v1/quick-start {"caseName":…}`, which **creates** `~/codeman-cases/<name>` unless the name is already a case. Any other path (a git worktree): `POST /api/v1/sessions {"workingDir":…}` then `POST /api/v1/sessions/:id/interactive`. Both install hooks by default, so expect full signals in either, and **verify** rather than assume. N workers means N worktrees | [§5.1](reference/verbs.md#51-where-to-spawn) |
|
||||
| know a new worker can accept a prompt | `GET .../wait-output?match=shift+tab&from=buffer` (urlencode the `+`) | [§5.2](reference/verbs.md#52-readiness) |
|
||||
| deliver a task **and** know when it finished | `POST .../input` with `"input":"…\r"`, `clientId`, `seq`, `"wait":true`. Resolves on `stop`, so it is only trustworthy in a **case Codeman created** (claude mode + hooks present). Costs the worker one billed turn | [§5.3](reference/verbs.md#53-send-a-task-and-wait) |
|
||||
| deliver a task **and** know when it finished | `POST .../input` with `"input":"…\r"`, `clientId`, `seq`, `"wait":true`. Resolves on `stop`, so it is trustworthy only where the workspace **has hooks** (claude mode; installed by default, but the operator can disable it and remote sessions never get them). Costs the worker one billed turn | [§5.3](reference/verbs.md#53-send-a-task-and-wait) |
|
||||
| know a hook-less worker finished | it has no `stop`, and `wait:true` there resolves on flapping `idle` **without erroring**: make it print a split, unique marker and `wait-output` on that instead | [§5.5](reference/verbs.md#55-markers-for-hook-less-workers) |
|
||||
| read the answer | `GET .../last-response`, **polled** (claude/codex only; empty for the other modes) | [§5.4](reference/verbs.md#54-read-the-answer) |
|
||||
| know if it is alive | `GET .../wait?until=exit&timeout=1000`: an immediate `signal:"exit"` means dead. `status` and `pid` both lie | [§5.6](reference/verbs.md#56-alive-and-stuck) |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ---- Codeman agent preamble 1.18.3 (seeded by Codeman at session spawn; the SKILL.md §0 bootstrap rewrites it when missing or stale) ----
|
||||
# ---- 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
|
||||
@@ -60,14 +60,14 @@ spawn_worker() {
|
||||
# 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
|
||||
# quick-start RESOLVES the name before creating: a linked case or an existing dir
|
||||
# wins over a fresh scratch case, so "created => hooks" is only true after this one
|
||||
# local grep (the same marker the server itself checks for). No marker means sendwait
|
||||
# would false-resolve on flapping idle, possibly inside the user's REAL repo: refuse
|
||||
# rather than run the job there.
|
||||
# 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 (linked or pre-existing?): pick an unused name, or work §5.1+§5.5 by hand" >&2
|
||||
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
|
||||
@@ -155,4 +155,4 @@ last_text() {
|
||||
# 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.18.3
|
||||
CODEMAN_PREAMBLE=1.19.0
|
||||
|
||||
@@ -251,10 +251,12 @@ that is expected, not a failure: read `terminal?tail=` and strip ANSI instead.
|
||||
**It means** that session has no Codeman hooks, so `stop` can never fire and the wait
|
||||
silently degraded to `idle`, which flaps mid-turn. Nothing rejected your request:
|
||||
`wait:true` (and even an explicit `until=stop`) is accepted because the 400 is about
|
||||
session **mode**, and the mode really is `claude`. Hooks are written only when Codeman
|
||||
**creates** the directory; a linked case or a raw `workingDir` gets none (an existing
|
||||
case that Codeman created earlier keeps the block it was given), see the table under
|
||||
[Signals by mode](#signals-by-mode). Measured: on a
|
||||
session **mode**, and the mode really is `claude`. Hooks are installed into every
|
||||
claude workspace at session create (synced `workspaceHooksEnabled`, default ON) and
|
||||
swept across recovered sessions at boot, so a linked case or a raw `workingDir` gets
|
||||
them too; with the setting off, on a remote session, or on a session from an older
|
||||
server, they are absent, see the table under
|
||||
[Signals by mode](#signals-by-mode). Measured before that changed: on a
|
||||
linked case whose `.claude/settings.local.json` carries env/model/permissions/statusLine
|
||||
and no `hooks` block, a `wait?until=stop,exit` parked for twelve consecutive 60 s rounds
|
||||
never resolved although the worker finished its turn.
|
||||
@@ -360,10 +362,10 @@ loop.
|
||||
⚠️ `caseName` resolves through the linked-cases registry first, so a name that happens
|
||||
to match a case the user linked in lands in that **real repo**, not a fresh scratch
|
||||
directory. Pick distinctive scratch names, and use a linked name deliberately when you
|
||||
do want a worker in an existing checkout. ⚠️ It also decides whether you get hooks:
|
||||
Codeman writes them only when it **creates** the directory, so a linked case or a raw
|
||||
path gives you a worker with no `stop` signal, while a scratch case Codeman created
|
||||
earlier keeps working signals ([Signals by mode](#signals-by-mode)).
|
||||
do want a worker in an existing checkout. It no longer decides whether you get hooks:
|
||||
every claude create path installs them, so a linked case and a raw path both get a
|
||||
`stop` signal unless the operator turned `workspaceHooksEnabled` off
|
||||
([Signals by mode](#signals-by-mode)).
|
||||
|
||||
**The two-step alternative, `POST /api/v1/sessions`.** Use it when you need a session in
|
||||
a directory that is not a case (body takes `workingDir`, `mode`, `name`, `effort`,
|
||||
@@ -614,35 +616,27 @@ Three bounded long-polls. Shared semantics:
|
||||
| `exit` | PTY exited or session deleted | every mode |
|
||||
|
||||
⚠️ **`claude` mode is necessary for `stop`/`blocked`, not sufficient. The real
|
||||
precondition is that the session's working directory has a Codeman hooks block**, and
|
||||
whether it does depends on who created the directory:
|
||||
precondition is that the session's working directory has a Codeman hooks block**, which
|
||||
is now installed by default rather than depending on who created the directory:
|
||||
|
||||
| The worker's directory | Hooks | `stop` / `blocked` | Synchronize with |
|
||||
|------------------------|-------|--------------------|------------------|
|
||||
| Codeman created it (`quick-start` with a NEW `caseName`, `POST /api/cases`, clone, docker quickcreate) | written at create | fire | send-and-wait on `stop` |
|
||||
| Codeman never created it (a linked case pointing at your own checkout, a raw `workingDir`) | none written | never fire | `wait-output` markers only |
|
||||
| any claude workspace, with `workspaceHooksEnabled` ON (the default) | installed at session create, add-only merge | fire | send-and-wait on `stop` |
|
||||
| the same, with the setting OFF and no block already on disk | none added | never fire | `wait-output` markers only |
|
||||
| a remote SSH session, a docker case that opted out, a workspace Codeman cannot write | none | never fire | `wait-output` markers only |
|
||||
| a session created by a pre-1.19.0 server and never restarted since | whatever it had | only if present | check, then choose |
|
||||
|
||||
⚠️ **Docker cases are the one exception.** For a docker case, quick-start writes hooks
|
||||
whenever `.claude/settings.local.json` is *missing* (`session-routes.ts:2836-2845`:
|
||||
absent means write, present means refresh), regardless of who created that host
|
||||
directory. There the discriminator really is "does the settings file exist". No
|
||||
downstream advice changes, since docker quickcreate is already on the create side.
|
||||
The install is an add-only merge, so a user's own hook entries survive and a malformed
|
||||
settings file is left untouched. Sessions recovered at server boot get the same sweep,
|
||||
which is what heals sessions created before this behavior existed. When in doubt, test
|
||||
it rather than reason about it: grep for `/api/hook-event` in
|
||||
`<casePath>/.claude/settings.local.json`.
|
||||
|
||||
⚠️ For every non-docker case the discriminator is **who created the directory, not
|
||||
whether it exists now**. A
|
||||
scratch case Codeman created last week still has its hooks block on disk, so
|
||||
`quick-start` against that existing name gets working `stop` signals. Only a directory
|
||||
Codeman never created lacks them. When in doubt, test it rather than reason about it:
|
||||
grep for `/api/hook-event` in `<casePath>/.claude/settings.local.json`.
|
||||
|
||||
`writeHooksConfig()` runs only on the create paths (`case-routes.ts:341`, `:520`,
|
||||
`:869`, `ralph-routes.ts:318`, `session-routes.ts:2799` inside
|
||||
`if (!existsSync(resolvedCasePath))`, `:2841` for docker). Quick-start against a
|
||||
directory that already exists takes the else-if branch and calls
|
||||
`refreshStaleCodemanHooks()`, which returns immediately when there is no
|
||||
`settings.local.json` and again when the hooks it finds are not ours
|
||||
(`hooks-config.ts:706-731`); it never *adds* a hooks block. `POST /api/cases/link` is
|
||||
not on that list at all: it only records a name-to-path entry. See
|
||||
Before 1.19.0, `writeHooksConfig()` ran only on the create paths and `quick-start`
|
||||
against an existing directory called `refreshStaleCodemanHooks()`, which never *adds* a
|
||||
block, so a linked case or a raw `workingDir` had no hooks at all. `POST
|
||||
/api/cases/link` still only records a name-to-path entry; what changed is that the
|
||||
session-create path installs hooks regardless of how the directory got there. See
|
||||
[symptom 8](#8-send-and-wait-resolves-instantly-with-signalidle-and-the-answer-is-last-turns).
|
||||
|
||||
Default `until` set: `stop,idle,exit`. On non-claude modes the server silently drops
|
||||
|
||||
@@ -196,12 +196,14 @@ idle:
|
||||
The contract an orchestrator follows for any fleet of two or more messaging workers.
|
||||
Every topology in the next section is this protocol plus a wiring diagram.
|
||||
|
||||
1. **Spawn with a name, and with hooks.** Use `quick-start` with `sessionName` (the
|
||||
`--name` gate above), and let it **CREATE** the case. ⚠️ Linking does NOT install
|
||||
hooks (`POST /api/cases/link` writes only the name-to-path entry), and neither does a
|
||||
bare `POST /api/sessions`; a worker in a directory Codeman did not create has no
|
||||
`stop`/`blocked` signals at all and every synchronization below degrades to output
|
||||
markers. The discriminator is who created the directory, not whether it exists now.
|
||||
1. **Spawn with a name, and confirm hooks.** Use `quick-start` with `sessionName` (the
|
||||
`--name` gate above). Session create installs the hooks block into the workspace
|
||||
whatever kind it is, so a linked case and a raw `POST /api/sessions` path both get
|
||||
`stop`/`blocked` by default. ⚠️ Not unconditionally: the operator can turn
|
||||
`workspaceHooksEnabled` off, remote SSH sessions never get hooks, and a session from
|
||||
an older server may have none, and without them every synchronization below degrades
|
||||
to output markers. Grep `<casePath>/.claude/settings.local.json` for
|
||||
`/api/hook-event` at spawn rather than inferring it from how the directory got there.
|
||||
2. **Readiness before addressing.** Flow 1's ladder per worker, then the availability
|
||||
probe. A worker that fails the probe is an HTTP worker for the rest of the run; that
|
||||
is a routing decision, not an error.
|
||||
|
||||
@@ -492,9 +492,9 @@ What breaks if you use send-and-wait anyway: `wait:true` is accepted (the 400 is
|
||||
*mode*, not about hooks, and these are claude-mode sessions), so the call falls back to
|
||||
the default set's `idle`, which is a heuristic that flaps mid-turn. You get a "finished"
|
||||
answer for a turn still running, and `last-response` then hands you the *previous*
|
||||
turn's text. The contrast is the lesson: a worker in a case Codeman created (Flow 1) has
|
||||
the hooks, so `stop` there is definitive and free. In a worktree you pay one marker per
|
||||
worker instead.
|
||||
turn's text. The contrast is the lesson: a worker whose workspace carries the hooks
|
||||
block (Flow 1, and by default any other workspace too) has a `stop` that is definitive
|
||||
and free. Where the block is absent you pay one marker per worker instead.
|
||||
|
||||
```bash
|
||||
declare -A TOK
|
||||
|
||||
@@ -30,28 +30,40 @@ wrong directory.** `quick-start` with a new `caseName` does not find your repo:
|
||||
| Where the work is | Call | Hooks, and therefore signals |
|
||||
|-------------------|------|------------------------------|
|
||||
| a fresh scratch dir (throwaway experiments) | `POST /api/v1/quick-start {"caseName":"scratch-1","mode":"claude"}` with a **new** case name | Codeman creates the directory and **writes hooks**: `stop` and `blocked` fire, send-and-wait is trustworthy |
|
||||
| a linked case (a real repo in the linked-cases registry) | same call with the linked name | **no hooks**, unless that repo already carries a Codeman hooks block from some earlier path. Check before relying on `stop` |
|
||||
| any other absolute path, e.g. a git worktree you made | `POST /api/v1/sessions {"workingDir":"/abs/path","mode":"claude"}` then `POST /api/v1/sessions/:id/interactive` | **no hooks**: no `stop`, no `blocked`, synchronize with markers ([§5.5](#55-markers-for-hook-less-workers)) |
|
||||
| a linked case (a real repo in the linked-cases registry) | same call with the linked name | **hooks installed at session create**, so `stop` fires here too. Not guaranteed: the operator can turn it off. Check |
|
||||
| any other absolute path, e.g. a git worktree you made | `POST /api/v1/sessions {"workingDir":"/abs/path","mode":"claude"}` then `POST /api/v1/sessions/:id/interactive` | same: **hooks installed at session create**, subject to the same setting. Check |
|
||||
|
||||
Read `.data.casePath` back from the `quick-start` response and check it is where you
|
||||
meant. `caseName` accepts letters, digits, `-` and `_` only, and it resolves through
|
||||
the linked-cases registry **first**, so a name that collides with something the user
|
||||
linked in lands in that real repo rather than a scratch dir.
|
||||
|
||||
**The rule is who created the directory.** Codeman writes hooks only where it created
|
||||
the workspace itself: `quick-start` on a NEW case name, `POST /api/cases`, the repo
|
||||
clone, the docker quick-create. Those hooks persist, so a scratch case created last
|
||||
week still has them today. A directory that already existed when Codeman first pointed
|
||||
at it never gets them: `POST /api/cases/link` writes only the name-to-path entry in
|
||||
`linked-cases.json`, and quick-start into an existing path runs
|
||||
`refreshStaleCodemanHooks()`, which by design returns immediately when there is no
|
||||
Codeman hooks block to refresh. Source-verified by exhaustive call-site grep, and
|
||||
measured: a worker in a linked case never resolved a parked `wait?until=stop,exit`
|
||||
across twelve consecutive 60 s rounds, although it had finished its turn.
|
||||
**The rule is a setting, not who created the directory.** Every claude create path
|
||||
(`POST /api/sessions`, `POST /api/quick-start`, and quick-start's docker branch) now
|
||||
installs the hooks block into the workspace, and the server sweeps the workspaces of
|
||||
sessions it recovers at boot. So a linked case, a cloned repo and a hand-made git
|
||||
worktree all get `stop`/`blocked`, not just a scratch case Codeman scaffolded. The
|
||||
install is an **add-only merge**: a user's own hook entries and every other settings
|
||||
key survive, and a malformed settings file is left alone.
|
||||
|
||||
**Check, do not assume.** Read `<casePath>/.claude/settings.local.json` with your own
|
||||
file tools and look for `/api/hook-event`. Present means `stop`/`blocked` will fire;
|
||||
absent means they never will.
|
||||
The gate is the synced **`workspaceHooksEnabled`** setting, **default ON** (an absent
|
||||
key counts as ON). Turned OFF, the old behavior returns exactly: an existing Codeman
|
||||
block is still refreshed when stale, but one is never added, and the boot sweep is
|
||||
skipped. Three cases stay hook-less regardless: **remote SSH sessions** (their
|
||||
`workingDir` is a path on another host), **docker cases that opted out**, and any
|
||||
workspace Codeman cannot write to.
|
||||
|
||||
Until this landed, hooks existed only where Codeman created the directory, and the
|
||||
gap was invisible: a worker in a linked case never resolved a parked
|
||||
`wait?until=stop,exit` across twelve consecutive 60 s rounds, although it had finished
|
||||
its turn. If you are driving an older server, assume that older rule.
|
||||
|
||||
**Check, do not assume.** This is now the load-bearing habit, because you cannot tell
|
||||
from the call which way the setting is set, and an old session created before the fix
|
||||
on a server that has not restarted still has nothing. Read
|
||||
`<casePath>/.claude/settings.local.json` with your own file tools and look for
|
||||
`/api/hook-event`. Present means `stop`/`blocked` will fire; absent means they never
|
||||
will, whatever kind of workspace it is.
|
||||
|
||||
⚠️ **The hook-less failure is silent, and it is the worst one in this skill.**
|
||||
`"wait":true` is still **accepted** on a hook-less claude session: the 400 you may be
|
||||
@@ -59,9 +71,10 @@ expecting is about session *mode*, not about hooks. With no `stop` to resolve on
|
||||
default signal set falls back to the heuristic `idle`, which flaps mid-turn, so
|
||||
send-and-wait returns "finished" while the worker is still working, and the
|
||||
`last-response` you read next hands you the **previous** turn's text. No error is
|
||||
raised anywhere. In any workspace Codeman did not create, use markers
|
||||
([§5.5](#55-markers-for-hook-less-workers)) and treat send-and-wait's answer as
|
||||
unreliable.
|
||||
raised anywhere. Hooks are installed by default now, so this is rarer than it was, but
|
||||
the failure is unchanged when it happens: in any workspace whose settings file has no
|
||||
`/api/hook-event`, use markers ([§5.5](#55-markers-for-hook-less-workers)) and treat
|
||||
send-and-wait's answer as unreliable.
|
||||
|
||||
Spawning at a raw path:
|
||||
|
||||
@@ -238,11 +251,13 @@ fi
|
||||
|
||||
### 5.3 Send a task and wait
|
||||
|
||||
⚠️ **Precondition: this is the call to prefer only for a claude worker in a workspace
|
||||
Codeman created**, because it is trustworthy only when the `stop` hook exists. On a
|
||||
linked case or a raw path it is accepted, resolves on flapping `idle`, and reports a
|
||||
turn as finished while it is still running, with no error anywhere. Check hooks first
|
||||
([§5.1](#51-where-to-spawn)); where they are absent, use markers
|
||||
⚠️ **Precondition: a claude worker whose workspace has the hooks block**, because
|
||||
this is trustworthy only when the `stop` hook exists. Every claude create path installs
|
||||
it by default now, so that is the normal case, but where it is absent (the setting off,
|
||||
a remote session, an older server) the call is still accepted, resolves on flapping
|
||||
`idle`, and reports a turn as finished while it is still running, with no error
|
||||
anywhere. Check hooks first ([§5.1](#51-where-to-spawn)); where they are absent, use
|
||||
markers
|
||||
([§5.5](#55-markers-for-hook-less-workers)).
|
||||
|
||||
It registers the waiter *before* typing,
|
||||
|
||||
Reference in New Issue
Block a user