mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
docs(skill): hooks are a setting now, not who created the directory
The workspace-hooks install makes the skill's central hooks rule wrong in the cautious direction. Six places told a worker that a linked case or a raw workingDir has no `stop`/`blocked` and that send-and-wait cannot be trusted there, so an agent would hand-roll output-marker synchronization in exactly the workspaces where `wait:true` now works. Rewritten against the setting rather than directory provenance: - verbs.md §5.1: the where-to-spawn table, the rule paragraph (now naming `workspaceHooksEnabled`, default ON, the add-only merge, and the boot sweep of recovered sessions), and the silent-failure warning. The three cases that stay hook-less regardless are called out: remote SSH sessions, docker cases that opted out, and a workspace Codeman cannot write to. - verbs.md §5.3: the send-and-wait precondition is "the workspace has the hooks block", not "a case Codeman created". - endpoints.md: the Signals-by-mode table is now keyed on the setting, with rows for OFF, for remote/docker-opt-out, and for a session from an older server. The old create-path grep list becomes a "before 1.18.x" note. - SKILL.md §2 + the cost list, recipes.md Flow-1 contrast, messaging.md step 1. "Check, do not assume" is kept and promoted to the load-bearing habit, because the setting is not visible from the call and a session created by an older server that has not restarted still has nothing. The `spawn_worker` hooks grep STAYS: it guards the setting being off, remote sessions, and older servers. Only its diagnostic changes, since "pick an unused name" is no longer the fix. That text lives in both the §0 heredoc and `preamble.sh`, which `test/agent-skill.test.ts` pins byte-identical, so both are patched with the same bytes. Docs only, no behavior change. 23 skill tests green, full test:ci 5109 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-9
@@ -138,14 +138,14 @@ spawn_worker() {
|
|||||||
# NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
|
# NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
|
||||||
[ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
|
[ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
|
||||||
[ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # only claude draws a composer
|
[ "$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
|
# The server installs hooks into every claude workspace now, so this grep normally
|
||||||
# wins over a fresh scratch case, so "created => hooks" is only true after this one
|
# passes; it stays because the install is gated on a setting the operator can turn
|
||||||
# local grep (the same marker the server itself checks for). No marker means sendwait
|
# off, remote sessions never get hooks, and a session created by an older server
|
||||||
# would false-resolve on flapping idle, possibly inside the user's REAL repo: refuse
|
# still has none. No marker means sendwait would false-resolve on flapping idle,
|
||||||
# rather than run the job there.
|
# possibly inside the user's REAL repo: refuse rather than run the job there.
|
||||||
cp=$(jq -r '.data.casePath // empty' <<<"$q")
|
cp=$(jq -r '.data.casePath // empty' <<<"$q")
|
||||||
grep -qs '/api/hook-event' "$cp/.claude/settings.local.json" || {
|
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; }
|
delete_session "$sid" >/dev/null; return 1; }
|
||||||
# Short composer wait FIRST, then the trust-dialog probe: a case still showing the
|
# 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
|
# dialog can never pass the composer wait, so probing early keeps a cold case from
|
||||||
@@ -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
|
`~/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
|
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)
|
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.
|
- `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
|
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
|
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 |
|
| 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) |
|
| 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) |
|
| 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) |
|
| 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) |
|
| 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) |
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ spawn_worker() {
|
|||||||
# NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
|
# NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
|
||||||
[ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
|
[ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
|
||||||
[ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # only claude draws a composer
|
[ "$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
|
# The server installs hooks into every claude workspace now, so this grep normally
|
||||||
# wins over a fresh scratch case, so "created => hooks" is only true after this one
|
# passes; it stays because the install is gated on a setting the operator can turn
|
||||||
# local grep (the same marker the server itself checks for). No marker means sendwait
|
# off, remote sessions never get hooks, and a session created by an older server
|
||||||
# would false-resolve on flapping idle, possibly inside the user's REAL repo: refuse
|
# still has none. No marker means sendwait would false-resolve on flapping idle,
|
||||||
# rather than run the job there.
|
# possibly inside the user's REAL repo: refuse rather than run the job there.
|
||||||
cp=$(jq -r '.data.casePath // empty' <<<"$q")
|
cp=$(jq -r '.data.casePath // empty' <<<"$q")
|
||||||
grep -qs '/api/hook-event' "$cp/.claude/settings.local.json" || {
|
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; }
|
delete_session "$sid" >/dev/null; return 1; }
|
||||||
# Short composer wait FIRST, then the trust-dialog probe: a case still showing the
|
# 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
|
# dialog can never pass the composer wait, so probing early keeps a cold case from
|
||||||
|
|||||||
@@ -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
|
**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:
|
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
|
`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
|
session **mode**, and the mode really is `claude`. Hooks are installed into every
|
||||||
**creates** the directory; a linked case or a raw `workingDir` gets none (an existing
|
claude workspace at session create (synced `workspaceHooksEnabled`, default ON) and
|
||||||
case that Codeman created earlier keeps the block it was given), see the table under
|
swept across recovered sessions at boot, so a linked case or a raw `workingDir` gets
|
||||||
[Signals by mode](#signals-by-mode). Measured: on a
|
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
|
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
|
and no `hooks` block, a `wait?until=stop,exit` parked for twelve consecutive 60 s rounds
|
||||||
never resolved although the worker finished its turn.
|
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
|
⚠️ `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
|
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
|
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:
|
do want a worker in an existing checkout. It no longer decides whether you get hooks:
|
||||||
Codeman writes them only when it **creates** the directory, so a linked case or a raw
|
every claude create path installs them, so a linked case and a raw path both get a
|
||||||
path gives you a worker with no `stop` signal, while a scratch case Codeman created
|
`stop` signal unless the operator turned `workspaceHooksEnabled` off
|
||||||
earlier keeps working signals ([Signals by mode](#signals-by-mode)).
|
([Signals by mode](#signals-by-mode)).
|
||||||
|
|
||||||
**The two-step alternative, `POST /api/v1/sessions`.** Use it when you need a session in
|
**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`,
|
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 |
|
| `exit` | PTY exited or session deleted | every mode |
|
||||||
|
|
||||||
⚠️ **`claude` mode is necessary for `stop`/`blocked`, not sufficient. The real
|
⚠️ **`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
|
precondition is that the session's working directory has a Codeman hooks block**, which
|
||||||
whether it does depends on who created the directory:
|
is now installed by default rather than depending on who created the directory:
|
||||||
|
|
||||||
| The worker's directory | Hooks | `stop` / `blocked` | Synchronize with |
|
| 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` |
|
| any claude workspace, with `workspaceHooksEnabled` ON (the default) | installed at session create, add-only merge | 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 |
|
| 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.18.x 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
|
The install is an add-only merge, so a user's own hook entries survive and a malformed
|
||||||
whenever `.claude/settings.local.json` is *missing* (`session-routes.ts:2836-2845`:
|
settings file is left untouched. Sessions recovered at server boot get the same sweep,
|
||||||
absent means write, present means refresh), regardless of who created that host
|
which is what heals sessions created before this behavior existed. When in doubt, test
|
||||||
directory. There the discriminator really is "does the settings file exist". No
|
it rather than reason about it: grep for `/api/hook-event` in
|
||||||
downstream advice changes, since docker quickcreate is already on the create side.
|
`<casePath>/.claude/settings.local.json`.
|
||||||
|
|
||||||
⚠️ For every non-docker case the discriminator is **who created the directory, not
|
Before 1.18.x, `writeHooksConfig()` ran only on the create paths and `quick-start`
|
||||||
whether it exists now**. A
|
against an existing directory called `refreshStaleCodemanHooks()`, which never *adds* a
|
||||||
scratch case Codeman created last week still has its hooks block on disk, so
|
block, so a linked case or a raw `workingDir` had no hooks at all. `POST
|
||||||
`quick-start` against that existing name gets working `stop` signals. Only a directory
|
/api/cases/link` still only records a name-to-path entry; what changed is that the
|
||||||
Codeman never created lacks them. When in doubt, test it rather than reason about it:
|
session-create path installs hooks regardless of how the directory got there. See
|
||||||
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
|
|
||||||
[symptom 8](#8-send-and-wait-resolves-instantly-with-signalidle-and-the-answer-is-last-turns).
|
[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
|
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.
|
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.
|
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
|
1. **Spawn with a name, and confirm hooks.** Use `quick-start` with `sessionName` (the
|
||||||
`--name` gate above), and let it **CREATE** the case. ⚠️ Linking does NOT install
|
`--name` gate above). Session create installs the hooks block into the workspace
|
||||||
hooks (`POST /api/cases/link` writes only the name-to-path entry), and neither does a
|
whatever kind it is, so a linked case and a raw `POST /api/sessions` path both get
|
||||||
bare `POST /api/sessions`; a worker in a directory Codeman did not create has no
|
`stop`/`blocked` by default. ⚠️ Not unconditionally: the operator can turn
|
||||||
`stop`/`blocked` signals at all and every synchronization below degrades to output
|
`workspaceHooksEnabled` off, remote SSH sessions never get hooks, and a session from
|
||||||
markers. The discriminator is who created the directory, not whether it exists now.
|
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
|
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
|
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.
|
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
|
*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"
|
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*
|
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
|
turn's text. The contrast is the lesson: a worker whose workspace carries the hooks
|
||||||
the hooks, so `stop` there is definitive and free. In a worktree you pay one marker per
|
block (Flow 1, and by default any other workspace too) has a `stop` that is definitive
|
||||||
worker instead.
|
and free. Where the block is absent you pay one marker per worker instead.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
declare -A TOK
|
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 |
|
| 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 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` |
|
| 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` | **no hooks**: no `stop`, no `blocked`, synchronize with markers ([§5.5](#55-markers-for-hook-less-workers)) |
|
| 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
|
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
|
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
|
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.
|
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 rule is a setting, not who created the directory.** Every claude create path
|
||||||
the workspace itself: `quick-start` on a NEW case name, `POST /api/cases`, the repo
|
(`POST /api/sessions`, `POST /api/quick-start`, and quick-start's docker branch) now
|
||||||
clone, the docker quick-create. Those hooks persist, so a scratch case created last
|
installs the hooks block into the workspace, and the server sweeps the workspaces of
|
||||||
week still has them today. A directory that already existed when Codeman first pointed
|
sessions it recovers at boot. So a linked case, a cloned repo and a hand-made git
|
||||||
at it never gets them: `POST /api/cases/link` writes only the name-to-path entry in
|
worktree all get `stop`/`blocked`, not just a scratch case Codeman scaffolded. The
|
||||||
`linked-cases.json`, and quick-start into an existing path runs
|
install is an **add-only merge**: a user's own hook entries and every other settings
|
||||||
`refreshStaleCodemanHooks()`, which by design returns immediately when there is no
|
key survive, and a malformed settings file is left alone.
|
||||||
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.
|
|
||||||
|
|
||||||
**Check, do not assume.** Read `<casePath>/.claude/settings.local.json` with your own
|
The gate is the synced **`workspaceHooksEnabled`** setting, **default ON** (an absent
|
||||||
file tools and look for `/api/hook-event`. Present means `stop`/`blocked` will fire;
|
key counts as ON). Turned OFF, the old behavior returns exactly: an existing Codeman
|
||||||
absent means they never will.
|
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.**
|
⚠️ **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
|
`"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
|
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
|
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
|
`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
|
raised anywhere. Hooks are installed by default now, so this is rarer than it was, but
|
||||||
([§5.5](#55-markers-for-hook-less-workers)) and treat send-and-wait's answer as
|
the failure is unchanged when it happens: in any workspace whose settings file has no
|
||||||
unreliable.
|
`/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:
|
Spawning at a raw path:
|
||||||
|
|
||||||
@@ -238,11 +251,13 @@ fi
|
|||||||
|
|
||||||
### 5.3 Send a task and wait
|
### 5.3 Send a task and wait
|
||||||
|
|
||||||
⚠️ **Precondition: this is the call to prefer only for a claude worker in a workspace
|
⚠️ **Precondition: a claude worker whose workspace has the hooks block**, because
|
||||||
Codeman created**, because it is trustworthy only when the `stop` hook exists. On a
|
this is trustworthy only when the `stop` hook exists. Every claude create path installs
|
||||||
linked case or a raw path it is accepted, resolves on flapping `idle`, and reports a
|
it by default now, so that is the normal case, but where it is absent (the setting off,
|
||||||
turn as finished while it is still running, with no error anywhere. Check hooks first
|
a remote session, an older server) the call is still accepted, resolves on flapping
|
||||||
([§5.1](#51-where-to-spawn)); where they are absent, use markers
|
`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)).
|
([§5.5](#55-markers-for-hook-less-workers)).
|
||||||
|
|
||||||
It registers the waiter *before* typing,
|
It registers the waiter *before* typing,
|
||||||
|
|||||||
Reference in New Issue
Block a user