Symptoms
dev_team.py's fix-issue-plan pipeline for Issue-219 got stuck in an infinite loop at
state: creating_pr. The dev-team:developer agent had already created PR #221
(#221) on branch dev/claude/Issue-219. The
create-pr-from-context skill's step 5 wrote the {"pr_url": "..."} deliverable to the
.pending/Issue-219__PR_URL.md scratch file (the write-scratch-deliverable half of step 5),
and merge_pending_deliverables() merged it into the context file's <!-- section:PR URL -->
body section — but the skill's other step-5 instruction ("Use the use-context-file skill to
write the returned PR URL to the pr_url frontmatter field") was never carried out, so
pr_url: frontmatter stayed empty.
Re-running dev_team.py after that kept re-emitting the exact same creating_pr spawn_agent
descriptor instead of progressing, risking a duplicate gh pr create on retry.
Root cause, confirmed by reading plugins/dev-team/skills/workflow-orchestrate/scripts/dev_team.py
and pipeline_context.py:
CreatePrStep.get_actions() (dev_team.py:755-773) gates re-dispatch on if ctx.pr_url: return [] only. ctx.pr_url is a frontmatter-only field populated at PipelineContext.load()
time from YAML frontmatter (pipeline_context.py:179) — it is never populated from the
merged PR URL body section at load time (unlike e.g. DebugStep, which correctly gates on
ctx.debug_report, itself populated straight from the Debug Report section body at load time
— pipeline_context.py:203). So when the section is merged but the frontmatter write didn't
happen, get_actions() returns the spawn_agent action again instead of [], and
DevTeamPipeline._do_get_actions_and_exit() (dev_team.py:1171-1227) exits with that action
before CreatePrStep.handle_results() — the only place that ever parses the PR URL section —
gets a chance to run.
- Worse and more destructive: that same
_do_get_actions_and_exit() call, on seeing a non-empty
actions list, does self.ctx.pending_agent = ...; self.ctx.save(self.context_path)
(dev_team.py:1225-1226) before exiting. PipelineContext.save()
(pipeline_context.py:52-134) fully regenerates the context file's body from only the
dataclass's own tracked fields (debug_report, brief, work_summaries, review_notes,
etc.) — there is no field at all for the PR URL section's content, so this save() call
silently evicts the already-merged PR URL section from the file, permanently losing the
PR URL that was already recorded there. Confirmed directly against the actual Issue-219 context
file: no <!-- section:PR URL --> block remains in it despite merge_pending_deliverables()
having merged one in.
Net effect: CreatePrStep can never self-recover from "section merged but frontmatter not yet
extracted" — it just re-spawns create-pr-from-context forever, and each re-entry actively
destroys the evidence needed to recover, not just fails to use it.
Workaround
Verified PR #221 is still open on dev/claude/Issue-219 (gh pr view 221 — matches), then
hand-set the context file's pr_url: frontmatter field directly to
https://github.com/jodavis/agent-plugins/pull/221. On the next dev_team.py invocation,
CreatePrStep.get_actions() sees non-empty ctx.pr_url and returns [] (the designed "recovery
re-entry" path), so control falls through to handle_results(), which sees ctx.pr_url already
set, calls _handle_agent_success(ctx), and returns the pr_created trigger without attempting
to create a second PR. This is safe to reuse any time this exact state is reached again (empty
pr_url frontmatter, state: creating_pr, and a real PR already known to exist for the branch) —
confirm the PR still exists and matches the working branch via gh pr view <n> before hand-editing,
then set pr_url: to its URL.
This does not fix the underlying dev_team.py/pipeline_context.py gap described above, which
will recur on any future run where the frontmatter write step is skipped or races with the
section merge (this same pipeline was noted to need to run again shortly for a stacked Issue-216
fix). A proper fix would mirror the DebugStep/debug_report pattern: add a pr_url_section
(or similarly named) field to PipelineContext, populate it from the PR URL body section at
load() time, serialize it in save() so it's never silently evicted, and have
CreatePrStep.get_actions()/handle_results() gate on and consume it the same way DebugStep
does for debug_report. troubleshooter.can-fix/can-push-fix are both unset in the merged
project configuration for this run, so that fix was not attempted here.
Symptoms
dev_team.py'sfix-issue-planpipeline for Issue-219 got stuck in an infinite loop atstate: creating_pr. Thedev-team:developeragent had already created PR #221(#221) on branch
dev/claude/Issue-219. Thecreate-pr-from-contextskill's step 5 wrote the{"pr_url": "..."}deliverable to the.pending/Issue-219__PR_URL.mdscratch file (thewrite-scratch-deliverablehalf of step 5),and
merge_pending_deliverables()merged it into the context file's<!-- section:PR URL -->body section — but the skill's other step-5 instruction ("Use the
use-context-fileskill towrite the returned PR URL to the
pr_urlfrontmatter field") was never carried out, sopr_url:frontmatter stayed empty.Re-running
dev_team.pyafter that kept re-emitting the exact samecreating_prspawn_agentdescriptor instead of progressing, risking a duplicate
gh pr createon retry.Root cause, confirmed by reading
plugins/dev-team/skills/workflow-orchestrate/scripts/dev_team.pyand
pipeline_context.py:CreatePrStep.get_actions()(dev_team.py:755-773) gates re-dispatch onif ctx.pr_url: return []only.ctx.pr_urlis a frontmatter-only field populated atPipelineContext.load()time from YAML frontmatter (
pipeline_context.py:179) — it is never populated from themerged
PR URLbody section at load time (unlike e.g.DebugStep, which correctly gates onctx.debug_report, itself populated straight from theDebug Reportsection body at load time—
pipeline_context.py:203). So when the section is merged but the frontmatter write didn'thappen,
get_actions()returns thespawn_agentaction again instead of[], andDevTeamPipeline._do_get_actions_and_exit()(dev_team.py:1171-1227) exits with that actionbefore
CreatePrStep.handle_results()— the only place that ever parses thePR URLsection —gets a chance to run.
_do_get_actions_and_exit()call, on seeing a non-emptyactionslist, doesself.ctx.pending_agent = ...; self.ctx.save(self.context_path)(
dev_team.py:1225-1226) before exiting.PipelineContext.save()(
pipeline_context.py:52-134) fully regenerates the context file's body from only thedataclass's own tracked fields (
debug_report,brief,work_summaries,review_notes,etc.) — there is no field at all for the
PR URLsection's content, so thissave()callsilently evicts the already-merged
PR URLsection from the file, permanently losing thePR URL that was already recorded there. Confirmed directly against the actual Issue-219 context
file: no
<!-- section:PR URL -->block remains in it despitemerge_pending_deliverables()having merged one in.
Net effect:
CreatePrStepcan never self-recover from "section merged but frontmatter not yetextracted" — it just re-spawns
create-pr-from-contextforever, and each re-entry activelydestroys the evidence needed to recover, not just fails to use it.
Workaround
Verified PR #221 is still open on
dev/claude/Issue-219(gh pr view 221— matches), thenhand-set the context file's
pr_url:frontmatter field directly tohttps://github.com/jodavis/agent-plugins/pull/221. On the nextdev_team.pyinvocation,CreatePrStep.get_actions()sees non-emptyctx.pr_urland returns[](the designed "recoveryre-entry" path), so control falls through to
handle_results(), which seesctx.pr_urlalreadyset, calls
_handle_agent_success(ctx), and returns thepr_createdtrigger without attemptingto create a second PR. This is safe to reuse any time this exact state is reached again (empty
pr_urlfrontmatter,state: creating_pr, and a real PR already known to exist for the branch) —confirm the PR still exists and matches the working branch via
gh pr view <n>before hand-editing,then set
pr_url:to its URL.This does not fix the underlying
dev_team.py/pipeline_context.pygap described above, whichwill recur on any future run where the frontmatter write step is skipped or races with the
section merge (this same pipeline was noted to need to run again shortly for a stacked Issue-216
fix). A proper fix would mirror the
DebugStep/debug_reportpattern: add apr_url_section(or similarly named) field to
PipelineContext, populate it from thePR URLbody section atload()time, serialize it insave()so it's never silently evicted, and haveCreatePrStep.get_actions()/handle_results()gate on and consume it the same wayDebugStepdoes for
debug_report.troubleshooter.can-fix/can-push-fixare both unset in the mergedproject configuration for this run, so that fix was not attempted here.