fix(api): dispatch-boundary guard rejects prose/markdown envelope (#581)#19
fix(api): dispatch-boundary guard rejects prose/markdown envelope (#581)#19miles990 wants to merge 1 commit into
Conversation
Shell worker received bounded-shell-probe retry envelopes (markdown with '## Retry Task: ...' headings) and ran them through bash, producing 'command not found' storms that tripped the autonomy-closure freq counter. Heuristic: first non-empty line starting with '##+ ' or '**Word' throws shell_received_prose typed error — loud once, not silent-N-times. ponytail: regex heuristic, upgrade to AST/allowlist if FPs appear. Closes miles990/mini-agent#581
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a0f6ecbc2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (/^#{2,}\s+\S/.test(firstLine) || /^\*\*[A-Za-z]/.test(firstLine)) { | ||
| throw new Error(`shell_received_prose: shell worker received prose/markdown, not a command. first_line=${JSON.stringify(firstLine.slice(0, 120))}`); |
There was a problem hiding this comment.
Move prose validation ahead of task submission
Because this guard throws from executeWorker after /dispatch has already submitted and started a task, malformed shell input is treated as an execution failure rather than request validation: wait=true returns a 500 with a taskId, while the default async path returns 200 {status:"running"} and records the failure later. For callers using this endpoint as a dispatch-boundary check, the prose envelope is not rejected as a bad request and still pollutes task history/callback flow instead of producing the intended immediate 400-style validation error.
Useful? React with 👍 / 👎.
| // ponytail: regex heuristic, upgrade to AST/allowlist if FPs appear. | ||
| { | ||
| const firstLine = shellCmd.split('\n').map(l => l.trim()).find(l => l.length > 0) ?? ''; | ||
| if (/^#{2,}\s+\S/.test(firstLine) || /^\*\*[A-Za-z]/.test(firstLine)) { |
There was a problem hiding this comment.
Allow valid shell comments before rejecting prose
This rejects any shell task whose first non-empty line is a double-hash comment, even though ## build is a valid bash comment and the legacy shell worker is documented/executed as a full bash string. A multiline task such as ## Run tests\nnpm test previously ran normally with the first line ignored by bash, but now fails with shell_received_prose before executing the real command.
Useful? React with 👍 / 👎.
| // ponytail: regex heuristic, upgrade to AST/allowlist if FPs appear. | ||
| { | ||
| const firstLine = shellCmd.split('\n').map(l => l.trim()).find(l => l.length > 0) ?? ''; | ||
| if (/^#{2,}\s+\S/.test(firstLine) || /^\*\*[A-Za-z]/.test(firstLine)) { |
There was a problem hiding this comment.
Catch top-level markdown headings too
The guard only recognizes headings with two or more # characters, so a prose envelope starting with a normal top-level markdown heading such as # Retry Task: foo still falls through to bash -c and produces the same command-not-found behavior this change is meant to stop. If this is intended as a dispatch-boundary markdown/prose guard, single-# headings need to be handled as well.
Useful? React with 👍 / 👎.
Summary
Closes #581 (38-day phantom P0, id
idx-af45d4ff-b34f-478e-a680-f3fe33bdaaf3).bounded-shell-proberetry envelope is markdown + prose with a single bash line buried inside;api.tswas dispatching the whole envelope tobash -c→ command-not-found storm → trippedfreq≥2inautonomy-closure-health.ts:315→ P0 re-surfaced forever.This adds a dispatch-boundary regex guard in
src/api.ts:315-323that rejects payloads that look like prose/markdown (multi-line + non-shell tokens) before they hit the worker shell.Files
src/api.ts+10 — guard regex + 400 responsetests/api.test.ts+8 —shell_received_prosetestTest plan
npx tsx --test --test-name-pattern="shell_received_prose"green locally[worker:shell] FAIL ... line 0: <prose>lines → falsifier dischargedVerification
Full triage in
.kuro/triage-581.md. Patch B (this PR) is the structural class-fix; Patch A (fix the retry generator upstream in mini-agent) is the orthogonal local-caller fix — either alone closes the symptom.🤖 Generated with Claude Code