Skip to content

creating_pr infinite loop: pr_url frontmatter never recovers from merged PR URL section, and gets evicted on retry #222

Description

@jodavis-claude

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:

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    troubleshooterAn issue discovered by the automated troubleshooter during a long-running workflow

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions