Skip to content

Source comments claim all triggers run on the CLI runtime; on the default driveMode: session none do (and the user-facing error cause is wrong) #771

Description

@edspencer

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:50cause: "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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    XSExtra small: under ~50 linesautoSuitable for autonomous agent implementation up to PRbugSomething isn't workingdocumentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions