Skip to content

T-LWEA-rename-spawn-task-pr-marker-to-break-done-pr-collision

Status: closed/superseded · Impact: high · Complexity: small

spawn-task-pr and task-work both emit terminal markers that begin with DONE pr=. When task-work Step 8 (post-mortem) dispatches spawn-from-post-mortem, which transitively calls spawn-task-pr per friction bullet, the inner skill’s DONE pr=<url> target=... branch=meta-task/... output bubbles up to the outer task-work LLM sub-agent. The outer agent — being an LLM eyeballing terminal markers, not a strict parser — treats it as task-work’s own DONE pr=#<N> final verdict and short-circuits, skipping Step 9 (sync with origin/main) and Step 10 (open the task’s own implementation PR). The task’s implementation commits and post-mortem commit land on the task/<basename> branch correctly, but the branch is never pushed and no implementation PR is opened — only the spawn-task-pr’s meta-task/<slug> PR. The first observed instances of this on 2026-05-28 left two tasks (promote-pluralize-to-shared-helper and lefthook-project-check-pre-commit) stuck in status: in-progress with implementation commits on their branches but no PR.

LocationRole today
plugin/skills/spawn-task-pr/SKILL.mdOutput contract section documents the success marker as DONE pr=<pr-url> target=<owner/name> branch=meta-task/<slug>. Same marker is the terminal stdout line at “Step 8. Report” (line ~269).
plugin/skills/spawn-task-pr/invariants.yamlMachine-readable invariants pin the marker text and shape; needs to update in lockstep with the SKILL.md change.
plugin/skills/task-work/SKILL.mdDocuments DONE pr=#<N> as one of the four task-work final verdicts (see “Final verdict — the only four task-work markers” section, ~line 948). Outer task-work sub-agents return this to the orchestrator.
plugin/skills/orchestrate/SKILL.mdStep 4 dispatch contract includes the task-work verdict allowlist regex `^(DONE pr=#\d+
plugin/entities/task/spawn-from-post-mortem.mdThe post-mortem procedure dispatched by task-work Step 8. Its “Commit and report” step (step 7) composes the SPAWNED-LOCAL/SPAWNED-CROSS-REPO/LINKED-EXISTING/SKIPPED/PATHS report that aggregates the results of per-bullet spawn-task-pr calls. Anywhere it references spawn-task-pr’s marker by literal text needs updating.

spawn-task-pr emits a terminal success marker that does NOT share a prefix with task-work’s DONE pr=#<N>. The replacement should keep the same semantic shape (one line, prefix + key=value pairs) and remain greppable. Proposed shape:

SPAWNED-PR pr=<pr-url> target=<owner/name> branch=meta-task/<slug>

The SPAWNED-PR prefix matches the existing SPAWNED-LOCAL / SPAWNED-CROSS-REPO family used by spawn-from-post-mortem and is mutually distinct from any task-work terminal marker. Every consumer of spawn-task-pr’s output (currently plugin/entities/task/spawn-from-post-mortem.md step 4b’s per-bullet dispatch result handling, plus its step 7 report aggregator) is updated to read the new marker.

After this task ships, task-work Step 8 → spawn-from-post-mortem → spawn-task-pr no longer surfaces a DONE pr= line to the outer task-work LLM sub-agent, and the outer agent reliably proceeds to Step 9 + Step 10 to push the task branch and open the implementation PR.

  1. Update plugin/skills/spawn-task-pr/SKILL.md: rename the marker from DONE pr=... to SPAWNED-PR pr=... in the “Output contract” section and at “Step 8. Report”. The EXISTING-PR pr=<url> marker (idempotency hit) and ERROR reason="..." markers do not collide and stay as-is.
  2. Update plugin/skills/spawn-task-pr/invariants.yaml to pin the new marker text (and remove the old).
  3. Update plugin/entities/task/spawn-from-post-mortem.md everywhere it references the old DONE pr= marker as the per-bullet success shape — step 4b’s dispatch result handling, step 5’s brief, step 7’s report aggregator. The SPAWNED-LOCAL / SPAWNED-CROSS-REPO counts in the final report continue to count SPAWNED-PR returns; the classification (Local vs Upstream-plugin vs Cross-project-request) is what drives which bucket a result lands in, not the marker text itself.
  4. Add an invariants entry to plugin/skills/task-work/invariants.yaml (if one exists; otherwise scope this AC to documentation only) asserting that task-work’s DONE pr= final verdict shape does NOT collide with any sub-skill it transitively invokes. The assertion is a regex grep across plugin/skills/*/SKILL.md and plugin/entities/*/*.md for ^DONE pr= lines outside task-work’s own SKILL.md — must return zero hits.
  5. Land a regression doc note in plugin/skills/task-work/SKILL.md near the Step 8 sub-agent dispatch section explaining the marker-collision class of bug (one paragraph, “if you add a new sub-skill that emits a DONE marker, do not let it share a prefix with task-work’s terminal verdicts”).
LocationKindChange
plugin/skills/spawn-task-pr/SKILL.mdmodifyRename terminal success marker DONE pr=...SPAWNED-PR pr=... in Output contract section and Step 8 (Report).
plugin/skills/spawn-task-pr/invariants.yamlmodifyUpdate pinned marker text to match.
plugin/entities/task/spawn-from-post-mortem.mdmodifyUpdate step 4b / step 5 / step 7 references to the per-bullet success marker from DONE pr=... to SPAWNED-PR pr=....
plugin/skills/task-work/invariants.yamlmodifyAdd a marker-collision invariant (regex grep across SKILL.md and entity docs for stray ^DONE pr= lines outside task-work’s own SKILL.md).
plugin/skills/task-work/SKILL.mdmodifyAppend a one-paragraph note near Step 8 dispatch warning future skill-authors not to share marker prefixes with task-work’s terminal verdicts.
  • AC-1: grep -rE '^DONE pr=' plugin/skills/ plugin/entities/ returns hits ONLY in plugin/skills/task-work/SKILL.md. No matches in plugin/skills/spawn-task-pr/SKILL.md, plugin/skills/spawn-task-pr/invariants.yaml, or plugin/entities/task/spawn-from-post-mortem.md.
  • AC-2: grep -rE '^SPAWNED-PR pr=' plugin/skills/ plugin/entities/ returns at least one hit in plugin/skills/spawn-task-pr/SKILL.md and at least one in plugin/entities/task/spawn-from-post-mortem.md.
  • AC-3: A subsequent end-to-end /sdlc:task-work run that generates at least one Local friction bullet (and therefore triggers spawn-task-pr) successfully proceeds through Step 9 + Step 10 and opens its own task/<basename> implementation PR. Manually verified by picking a small ready task and watching the orchestrator dispatch run.
  • AC-4: plugin/skills/task-work/invariants.yaml carries a marker-collision check (or the equivalent assertion in plugin/scripts/lint_skill_prose.py) that future PRs adding new sub-skills with DONE pr= markers fail.
  • Recovering the two specific stuck tasks (2026-05-21-promote-pluralize-to-shared-helper and 2026-05-21-lefthook-project-check-pre-commit) — that recovery is being done manually outside this task (push branches, open PRs, transition leases).
  • Renaming the spawn-task-pr skill itself or restructuring the post-mortem flow. This is a marker-text fix; the architecture is fine.
  • Audit of every other skill for similar marker-prefix collisions beyond what AC-4’s check covers.
  • none

Surfaced on 2026-05-28 during the first live multi-task orchestrator tick. Two of three task-work runs that produced friction-bullet post-mortems landed implementation commits on their task branches but never pushed/opened a PR — they reported the spawn-task-pr meta PR as their DONE pr= verdict and exited. The third successful run in the same tick (extract-phrase-in-prose-shared-helper) didn’t trip the bug because its post-mortem had no Local-classified friction bullets, so spawn-task-pr never ran. Root cause confirmed by inspecting the worktree commit histories of the two stuck tasks plus the spawn-task-pr/SKILL.md:269 vs task-work/SKILL.md:916 marker contracts. The renamed-to-spawn-task-pr refactor (commit 4b84272) introduced the collision by reusing the DONE pr= prefix on a now-LLM-visible inner marker.


← Back to Tasks