~bigbes/agents-dev · parade

main · last commit 13 days ago · 7g0stsfu

← Back to the parade

ah-1cx.16 runner: reject a blank rendered PROMPT.md at Start instead of burning an attempt Past Stand

status: closed P3 bug milestone:v0.2.0
bd reopen ah-1cx.16
Created byEugene Blikh
Ownerbigbes@gmail.com
Created2026-08-04T23:46:15Z
Started2026-08-04T23:54:12Z
Updated2026-08-05T00:23:21Z
Closed2026-08-05T00:23:21Z
Description
Found by the adversarial review of ah-tqc and empirically reproduced against the installed pi 0.82.x.

pi's dist/modes/print-mode.js:94 guards the entire run with 'if (initialMessage)', so an empty or whitespace-only prompt makes pi do nothing and exit 0 with a one-line event stream containing only the session header. runner.prepareTaskDir writes s.Prompt verbatim with no non-empty check, and nothing in reconcile guards it either.

ah-tqc's second Errored() arm (SessionID != "" && AssistantMessages == 0) now CATCHES this, so the card correctly fails instead of sailing to In Review — but only AFTER the attempt is spent, a worktree is created, a zellij pane is spawned and an attempt number is consumed. A strings.TrimSpace(s.Prompt) == "" rejection in Start would fail it loudly and instantly with an actionable message naming the role and template.

Deliberately left out of the ah-tqc fix because the check arguably belongs on the reconcile side of the boundary — the reconciler is what renders the template and knows which role/prompt file produced the empty result, so it can say WHY. Decide which side owns it: a Start-side guard is a cheap backstop that cannot explain itself, a reconcile-side check can name the template but leaves the port unguarded against a future caller.
Acceptance criteria
A role whose template renders to whitespace fails before a worktree or pane is created, with an error naming the role and the prompt file. The ah-tqc Errored() arm stays as the backstop and its test still passes.

Depends on

  • ah-1cx — Hardening: post-audit bug sweep (2026-07-20) parent-child open

Depended on by

Nothing depends on this issue.

No comments.

Close reason

Done in 8716a2a. Confirmed: prepareTaskDir writes s.Prompt verbatim and nothing upstream checked it, so a role template rendering to whitespace produced a pi run that did nothing and exited 0 — caught by ah-tqc's second Errored() arm, but only AFTER a worktree, a zellij pane and an attempt number were spent, and with no way to say which role or template was at fault. The guard went on the RECONCILE side, inside renderPrompt: the single seam both claim and launchResume render through, and the only layer holding the role name and the template path — the two facts an operator needs. The error flows into the existing SPEC §12 claim-failure path (card → failed, 'claim failed' comment, claim_failed event) with no new machinery. The port is deliberately NOT guarded twice: ah-tqc's arm is the port's own backstop and stays intact, so a blank prompt reaching Start by some future route still finalizes as an error rather than sailing to In Review; ports.StartSpec.Prompt now documents that split as the contract. Note the check is on the TEMPLATE's output, before the artifact manifest and Context Pack are appended — a role whose own template says nothing is a broken config even if the card happens to carry text. Cover: two prompt_test.go subtests plus TestClaimRejectsBlankRenderedPrompt.
  • Eugene Blikh added under epic ah-1cx · 2026-08-05T02:46:14Z
  • Eugene Blikh created the issue · 2026-08-05T02:46:14Z
  • Eugene Blikh changed status to in_progress · 2026-08-05T02:54:11Z
  • Eugene Blikh added label milestone:v0.2.0 · 2026-08-05T03:10:44Z
  • Eugene Blikh closed the issue · 2026-08-05T03:23:21Z
    Done in 8716a2a. Confirmed: prepareTaskDir writes s.Prompt verbatim and nothing upstream checked it, so a role template rendering to whitespace produced a pi run that did nothing and exited 0 — caught by ah-tqc's second Errored() arm, but only AFTER a worktree, a zellij pane and an attempt number were spent, and with no way to say which role or template was at fault. The guard went on the RECONCILE side, inside renderPrompt: the single seam both claim and launchResume render through, and the only layer holding the role name and the template path — the two facts an operator needs. The error flows into the existing SPEC §12 claim-failure path (card → failed, 'claim failed' comment, claim_failed event) with no new machinery. The port is deliberately NOT guarded twice: ah-tqc's arm is the port's own backstop and stays intact, so a blank prompt reaching Start by some future route still finalizes as an error rather than sailing to In Review; ports.StartSpec.Prompt now documents that split as the contract. Note the check is on the TEMPLATE's output, before the artifact manifest and Context Pack are appended — a role whose own template says nothing is a broken config even if the card happens to carry text. Cover: two prompt_test.go subtests plus TestClaimRejectsBlankRenderedPrompt.
Stored rows — what this pane was built from, as read
issues 1 row
id ah-1cx.16
content_hash 7eff6608348ecbc852835f358a9a699e40ec1398043f10acc52a4b282570f971
title runner: reject a blank rendered PROMPT.md at Start instead of burning an attempt
description Found by the adversarial review of ah-tqc and empirically reproduced against the installed pi 0.82.x. pi's dist/modes/print-mode.js:94 guards the entire run with 'if (initialMessage)', so an empty or whitespace-only prompt makes pi do nothing and exit 0 with a one-line event stream containing only the session header. runner.prepareTaskDir writes s.Prompt verbatim with no non-empty check, and nothing in reconcile guards it either. ah-tqc's second Errored() arm (SessionID != "" && AssistantMessages == 0) now CATCHES this, so the card correctly fails instead of sailing to In Review — but only AFTER the attempt is spent, a worktree is created, a zellij pane is spawned and an attempt number is consumed. A strings.TrimSpace(s.Prompt) == "" rejection in Start would fail it loudly and instantly with an actionable message naming the role and template. Deliberately left out of the ah-tqc fix because the check arguably belongs on the reconcile side of the boundary — the reconciler is what renders the template and knows which role/prompt file produced the empty result, so it can say WHY. Decide which side owns it: a Start-side guard is a cheap backstop that cannot explain itself, a reconcile-side check can name the template but leaves the port unguarded against a future caller.
design
acceptance_criteria A role whose template renders to whitespace fails before a worktree or pane is created, with an error naming the role and the prompt file. The ah-tqc Errored() arm stays as the backstop and its test still passes.
notes
status closed
priority 3
issue_type bug
assignee NULL
estimated_minutes NULL
created_at 2026-08-04T23:46:15Z
created_by Eugene Blikh
owner bigbes@gmail.com
updated_at 2026-08-05T00:23:21Z
closed_at 2026-08-05T00:23:21Z
closed_by_session
external_ref NULL
spec_id
compaction_level 0
compacted_at NULL
compacted_at_commit NULL
original_size NULL
sender
ephemeral 0
wisp_type
pinned 0
is_template 0
mol_type
work_type
source_system
metadata �{}
source_repo
close_reason Done in 8716a2a. Confirmed: prepareTaskDir writes s.Prompt verbatim and nothing upstream checked it, so a role template rendering to whitespace produced a pi run that did nothing and exited 0 — caught by ah-tqc's second Errored() arm, but only AFTER a worktree, a zellij pane and an attempt number were spent, and with no way to say which role or template was at fault. The guard went on the RECONCILE side, inside renderPrompt: the single seam both claim and launchResume render through, and the only layer holding the role name and the template path — the two facts an operator needs. The error flows into the existing SPEC §12 claim-failure path (card → failed, 'claim failed' comment, claim_failed event) with no new machinery. The port is deliberately NOT guarded twice: ah-tqc's arm is the port's own backstop and stays intact, so a blank prompt reaching Start by some future route still finalizes as an error rather than sailing to In Review; ports.StartSpec.Prompt now documents that split as the contract. Note the check is on the TEMPLATE's output, before the artifact manifest and Context Pack are appended — a role whose own template says nothing is a broken config even if the card happens to carry text. Cover: two prompt_test.go subtests plus TestClaimRejectsBlankRenderedPrompt.
event_kind
actor
target
payload
await_type
await_id
timeout_ns 0
waiters
hook_bead
role_bead
agent_state
last_activity NULL
role_type
rig
due_at NULL
defer_until NULL
no_history 0
started_at 2026-08-04T23:54:12Z
is_blocked 0
labels 1 row
issue_id ah-1cx.16
label milestone:v0.2.0
dependencies 1 row
id 952bae33-e2bd-50a4-b0af-21a89d8b111b
issue_id ah-1cx.16
type parent-child
created_at 2026-08-05T02:46:14Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-1cx
depends_on_wisp_id NULL
depends_on_external NULL
events 4 rows
id 019fcf2b-b0cb-729f-86c8-2a97e7613e88
issue_id ah-1cx.16
event_type created
actor Eugene Blikh
old_value
new_value
comment NULL
created_at 2026-08-05T02:46:14Z
id 019fcf32-f6e8-7d23-9418-066666f4cd8e
issue_id ah-1cx.16
event_type status_changed
actor Eugene Blikh
old_value {"id":"ah-1cx.16","title":"runner: reject a blank rendered PROMPT.md at Start instead of burning an attempt","description":"Found by the adversarial review of ah-tqc and empirically reproduced against the installed pi 0.82.x.\n\npi's dist/modes/print-mode.js:94 guards the entire run with 'if (initialMessage)', so an empty or whitespace-only prompt makes pi do nothing and exit 0 with a one-line event stream containing only the session header. runner.prepareTaskDir writes s.Prompt verbatim with no non-empty check, and nothing in reconcile guards it either.\n\nah-tqc's second Errored() arm (SessionID != \"\" \u0026\u0026 AssistantMessages == 0) now CATCHES this, so the card correctly fails instead of sailing to In Review — but only AFTER the attempt is spent, a worktree is created, a zellij pane is spawned and an attempt number is consumed. A strings.TrimSpace(s.Prompt) == \"\" rejection in Start would fail it loudly and instantly with an actionable message naming the role and template.\n\nDeliberately left out of the ah-tqc fix because the check arguably belongs on the reconcile side of the boundary — the reconciler is what renders the template and knows which role/prompt file produced the empty result, so it can say WHY. Decide which side owns it: a Start-side guard is a cheap backstop that cannot explain itself, a reconcile-side check can name the template but leaves the port unguarded against a future caller.","acceptance_criteria":"A role whose template renders to whitespace fails before a worktree or pane is created, with an error naming the role and the prompt file. The ah-tqc Errored() arm stays as the backstop and its test still passes.","status":"open","priority":3,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-08-04T23:46:15Z","created_by":"Eugene Blikh","updated_at":"2026-08-04T23:46:15Z"}
new_value {"status":"in_progress"}
comment NULL
created_at 2026-08-05T02:54:11Z
id 019fcf42-1efe-7eff-bdff-cb699b7c2c5f
issue_id ah-1cx.16
event_type label_added
actor Eugene Blikh
old_value NULL
new_value NULL
comment Added label: milestone:v0.2.0
created_at 2026-08-05T03:10:44Z
id 019fcf4d-a970-7822-9012-d72fa3dc5ad0
issue_id ah-1cx.16
event_type closed
actor Eugene Blikh
old_value
new_value Done in 8716a2a. Confirmed: prepareTaskDir writes s.Prompt verbatim and nothing upstream checked it, so a role template rendering to whitespace produced a pi run that did nothing and exited 0 — caught by ah-tqc's second Errored() arm, but only AFTER a worktree, a zellij pane and an attempt number were spent, and with no way to say which role or template was at fault. The guard went on the RECONCILE side, inside renderPrompt: the single seam both claim and launchResume render through, and the only layer holding the role name and the template path — the two facts an operator needs. The error flows into the existing SPEC §12 claim-failure path (card → failed, 'claim failed' comment, claim_failed event) with no new machinery. The port is deliberately NOT guarded twice: ah-tqc's arm is the port's own backstop and stays intact, so a blank prompt reaching Start by some future route still finalizes as an error rather than sailing to In Review; ports.StartSpec.Prompt now documents that split as the contract. Note the check is on the TEMPLATE's output, before the artifact manifest and Context Pack are appended — a role whose own template says nothing is a broken config even if the card happens to carry text. Cover: two prompt_test.go subtests plus TestClaimRejectsBlankRenderedPrompt.
comment NULL
created_at 2026-08-05T03:23:21Z