Summary
Three source comments — one of which is rendered to users as an error cause — state that the post-turn sweeper and all triggers run as one-shot trigger() calls on herdctl's CLI runtime. Triggers do not. A trigger resolves its drive mode exactly like a chat does, and on the default session mode it runs the SDK runtime and never consults PATH. Only the sweeper is unconditional.
Evidence
The claim:
packages/server/src/cli/paddock.ts:83-86 — "Chat turns do NOT need this… But the post-turn sweeper and all triggers are one-shot trigger() calls on the CLI runtime, which does execa(\"claude\", …)"
packages/server/src/agent-errors.ts:50 — cause: "the \claude` CLI is not on PATH (needed by the sweeper and triggers)"— this string is **user-facing**, surfaced byclassifyAgentError`
Dockerfile:7-9 — "Only the CLI runtime uses the PATH binary… i.e. the sweeper, triggers, and driveMode: batch turns"
Ground truth:
packages/server/src/ws-triggers.ts:70-74 resolves a trigger's drive mode with the same precedence a chat uses — per-project override else cfg.driveMode:
function resolveDriveMode(project) {
return project.driveMode && isKnownDriveMode(project.driveMode)
? project.driveMode
: deps.cfg.driveMode;
}
packages/server/src/ws-triggers.ts:189 passes that resolved mode into startAgentTurn (driveMode: resolveDriveMode(project)), which branches on driveMode === "session" (packages/server/src/ws-turn.ts:551, :558, :716) and drives openChatSession — the SDK runtime, which resolves its executable from the Agent SDK's pinned platform package via require.resolve and never reads PATH (packages/server/src/herdctl.ts:30, :1458, packages/server/src/herdctl-agent-config.ts:395).
- The default drive mode is
session (packages/server/src/models.ts:168, DEFAULT_DRIVE_MODE), so on a stock instance no trigger touches PATH.
- The sweeper genuinely is unconditional:
HerdctlService.runSweeper (packages/server/src/herdctl.ts:1607-1625) calls this.manager.trigger(...) directly with no drive-mode input.
So the accurate statement is: the sweeper always uses the CLI runtime; triggers use it only on driveMode: batch.
Why it matters
- Four documentation pages inherited this error straight from these comments. Fixing only the docs guarantees the same regression at the next docs pass — the comments are the upstream source.
agent-errors.ts:50 is wrong advice to a user. A trigger that fails on the default session mode is not failing because claude is off PATH, and the message sends the operator to check the wrong thing.
- It matters for image/deployment reasoning too (
Dockerfile): the set of things that need the PATH binary is smaller than documented.
Suggested fix
Correct all three to distinguish the sweeper (always CLI runtime) from triggers (CLI runtime only on batch). For agent-errors.ts, either narrow the cause text (e.g. "needed by the sweeper, and by triggers/turns on driveMode: batch") or keep it generic about the CLI runtime rather than naming triggers unconditionally.
Also, in the same family: a stale comment naming tools that no longer exist
packages/server/src/config.ts:446-448 documents hooksMcpEnabled as gating "the mcp__paddock_manage__{list,set,remove}_hook tools that let a project agent declare/edit/delete its own event hooks".
Those tool names are gone. The flag now gates the unified trigger tools — list_triggers, set_trigger, remove_trigger, run_trigger (packages/server/src/self-mcp.ts:403, :417, :499, :515; the block comment at :543-544 says so), resolved at dispatch in packages/server/src/ws.ts:897-902 via resolveHooksMcpEnabled. packages/server/src/self-mcp.ts:604 carries the same stale "(set/remove/list_hook) verbs" phrasing.
Docs-only impact, but it is a comment on a config field that misleads the next reader about which capability the flag actually grants. Filed here rather than separately because it is the same defect class — a source comment that the docs then inherit — and the fix is the same kind of edit.
Summary
Three source comments — one of which is rendered to users as an error cause — state that the post-turn sweeper and all triggers run as one-shot
trigger()calls on herdctl's CLI runtime. Triggers do not. A trigger resolves its drive mode exactly like a chat does, and on the defaultsessionmode it runs the SDK runtime and never consultsPATH. Only the sweeper is unconditional.Evidence
The claim:
packages/server/src/cli/paddock.ts:83-86— "Chat turns do NOT need this… But the post-turn sweeper and all triggers are one-shottrigger()calls on the CLI runtime, which doesexeca(\"claude\", …)"packages/server/src/agent-errors.ts:50—cause: "the \claude` CLI is not on PATH (needed by the sweeper and triggers)"— this string is **user-facing**, surfaced byclassifyAgentError`Dockerfile:7-9— "Only the CLI runtime uses the PATH binary… i.e. the sweeper, triggers, anddriveMode: batchturns"Ground truth:
packages/server/src/ws-triggers.ts:70-74resolves a trigger's drive mode with the same precedence a chat uses — per-project override elsecfg.driveMode:packages/server/src/ws-triggers.ts:189passes that resolved mode intostartAgentTurn(driveMode: resolveDriveMode(project)), which branches ondriveMode === "session"(packages/server/src/ws-turn.ts:551,:558,:716) and drivesopenChatSession— the SDK runtime, which resolves its executable from the Agent SDK's pinned platform package viarequire.resolveand never readsPATH(packages/server/src/herdctl.ts:30,:1458,packages/server/src/herdctl-agent-config.ts:395).session(packages/server/src/models.ts:168,DEFAULT_DRIVE_MODE), so on a stock instance no trigger touchesPATH.HerdctlService.runSweeper(packages/server/src/herdctl.ts:1607-1625) callsthis.manager.trigger(...)directly with no drive-mode input.So the accurate statement is: the sweeper always uses the CLI runtime; triggers use it only on
driveMode: batch.Why it matters
agent-errors.ts:50is wrong advice to a user. A trigger that fails on the defaultsessionmode is not failing becauseclaudeis offPATH, and the message sends the operator to check the wrong thing.Dockerfile): the set of things that need the PATH binary is smaller than documented.Suggested fix
Correct all three to distinguish the sweeper (always CLI runtime) from triggers (CLI runtime only on
batch). Foragent-errors.ts, either narrow the cause text (e.g. "needed by the sweeper, and by triggers/turns ondriveMode: batch") or keep it generic about the CLI runtime rather than naming triggers unconditionally.Also, in the same family: a stale comment naming tools that no longer exist
packages/server/src/config.ts:446-448documentshooksMcpEnabledas gating "themcp__paddock_manage__{list,set,remove}_hooktools that let a project agent declare/edit/delete its own event hooks".Those tool names are gone. The flag now gates the unified trigger tools —
list_triggers,set_trigger,remove_trigger,run_trigger(packages/server/src/self-mcp.ts:403,:417,:499,:515; the block comment at:543-544says so), resolved at dispatch inpackages/server/src/ws.ts:897-902viaresolveHooksMcpEnabled.packages/server/src/self-mcp.ts:604carries the same stale "(set/remove/list_hook) verbs" phrasing.Docs-only impact, but it is a comment on a config field that misleads the next reader about which capability the flag actually grants. Filed here rather than separately because it is the same defect class — a source comment that the docs then inherit — and the fix is the same kind of edit.