Skip to content

fix(pipeline): don't open a PR for docs that already have one - #449

Merged
shirgoldbird merged 1 commit into
mainfrom
docs/pipeline-dedupe-open-prs
Sep 22, 2026
Merged

shirgoldbird merged 1 commit into
mainfrom
docs/pipeline-dedupe-open-prs

Conversation

@shirgoldbird

Copy link
Copy Markdown
Member

Summary

The docs pipeline opened a new PR for the same missing pages on every run. Eight were open when this was written (#415, #422, #423, #428, #430, #435, #446, #447), all covering the same two gaps.

Why: a gap stays open until its PR merges, but detect_gaps.py re-derives gaps from scratch each run. Nothing told a run that the gap it just found was already sitting in review, so each spec change drafted, reviewed and shipped it again.

What changed

New pipeline/open_prs.py, wired in at two points:

  • generate.py drops gaps an open PR already covers, before any model call, so a duplicate run costs nothing instead of a full generate → review cycle.
  • ship.py refuses to open a PR when an open one covers the whole run, and stamps each PR body with a <!-- docs-pipeline-gaps: [...] --> marker so later runs can recognize it.

Both exit 3 ("nothing to do"), and run.py stops the chain on that rather than reading it as success or failure. That mattered: the later steps run on --latest, so continuing would have promoted and reshipped an older run's drafts.

A gap counts as in flight on any of three signals, because none alone is enough:

Signal Source Why it's needed
Gap key PR-body marker written by ship.py The model invents the filename, so two runs of one gap produce different slugs (...for-a-resource.mdx vs ...for-a-deepl-api-resource.mdx) that path matching can't connect
File paths The PR's changed files Gaps with a predictable target, including PRs the pipeline didn't open (a human writing the same page)
covers: frontmatter The PR's diff missing_group_coverage is defined by which endpoint groups no page declares it covers, so this recognizes the PRs that predate the marker

Two deliberate choices:

  • Partial overlap still ships. The run carries work no open PR has, and dropping it would lose that work silently. The overlap is reported, not blocked.
  • If gh is unreachable the check is skipped with a warning, rather than blocking the pipeline on a missing CLI. It allows a duplicate; it never stops a run.

Escape hatches: generate.py --ignore-open-prs, ship.py --force-new-pr.

Adjacent bug

rework.py recorded no page in its gap, so every rework of the same family shared one gap key (rework:admin:-:-) and task B would have been read as a duplicate of task A. Now each rework carries the page it targets.

Verification

Run against this repo's real open PRs, the two gaps behind all eight duplicates are recognized:

Skipping 2 gap(s) already covered by an open PR:
  - missing_group_coverage (Voice): a page covering 'Translate Audio Files' is already in PR #447
  - missing_group_coverage (Languages): a page covering 'Languages' is already in PR #447
  Review or close #447 to let the pipeline redraft these.

ship.py was also tested end-to-end against #447: it refused before creating a branch, and shipped normally for a page no open PR touches.

New pipeline/test_open_prs.py — 24 tests, stdlib only, gh stubbed so a stray subprocess fails the test. Wired into test-scripts.yml on any pipeline/** change (the pipeline had no tests before).

python pipeline/test_open_prs.py

How to review

  1. pipeline/open_prs.py is the whole mechanism; the rest is wiring.
  2. pipeline/README.md has a new Duplicate PRs section with the behavior and the overrides.
  3. docs: pipeline-generated pages (?, Languages, Voice) #446, docs: pipeline-generated pages (?, Languages, Voice) #435, docs: pipeline-generated pages (?, Languages, Voice) #430, docs: pipeline-generated pages (?, Languages, Voice) #428, docs: pipeline-generated pages (?, Languages, Voice) #423, docs: pipeline-generated pages (?, Languages, Voice) #422 and docs: pipeline-generated pages (?, Languages, Voice) #415 are superseded by docs: pipeline-generated pages (?, Languages, Voice) #447 and are being closed separately.

🤖 Generated with Claude Code

A gap stays open until its PR merges, but detect_gaps re-derives gaps from
scratch every run. So each spec change re-detected the same gaps and shipped
them again: 8 open PRs (#415 through #447), all for the same two missing pages.

New pipeline/open_prs.py, wired in at two points:

- generate.py drops gaps an open PR already covers, before any model call, so
  a duplicate run costs nothing instead of a full generate -> review cycle.
- ship.py refuses to open a PR when an open one covers the whole run, and
  stamps each PR body with the gap keys it covers so later runs recognize it.

Both exit 3 ("nothing to do"), and run.py stops the chain on that rather than
reading it as success or failure. That mattered: the later steps run on
--latest, so continuing would promote and reship an older run's drafts.

A gap counts as in flight on any of three signals, because none alone is
enough:

- Gap key, from the PR-body marker. The model invents the filename, so two
  runs of one gap produce different slugs that path matching can't connect.
- Changed file paths, for gaps with a predictable target. Also catches PRs
  the pipeline didn't open.
- covers: frontmatter in the PR diff. missing_group_coverage is defined by
  which endpoint groups no page declares it covers, so this recognizes the
  PRs that predate the marker.

Partial overlap still ships: the run carries work no open PR has, and
dropping it would lose that work silently. If gh is unreachable the check is
skipped with a warning rather than blocking the pipeline. Escape hatches:
generate.py --ignore-open-prs, ship.py --force-new-pr.

Also gives rework gaps a per-page identity. Without it every rework of the
same family shared one gap key ("rework:admin:-:-") and task B would have
been read as a duplicate of task A.

Tests: pipeline/test_open_prs.py (24, stdlib only, gh stubbed so a stray
subprocess fails the test), run in CI on any pipeline/** change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@mintlify

mintlify Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
deepl-c950b784 🟢 Ready View Preview Sep 22, 2026, 4:24 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

This branch was successfully deployed

1 active deployment
staging — 7c0bf79a Deployed Sep 22, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant