fix(pipeline): don't open a PR for docs that already have one - #449
Merged
Merged
Conversation
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>
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
This was referenced Sep 22, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyre-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.pydrops gaps an open PR already covers, before any model call, so a duplicate run costs nothing instead of a full generate → review cycle.ship.pyrefuses 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.pystops 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:
ship.py...for-a-resource.mdxvs...for-a-deepl-api-resource.mdx) that path matching can't connectcovers:frontmattermissing_group_coverageis defined by which endpoint groups no page declares it covers, so this recognizes the PRs that predate the markerTwo deliberate choices:
ghis 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.pyrecorded 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:
ship.pywas 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,ghstubbed so a stray subprocess fails the test. Wired intotest-scripts.ymlon anypipeline/**change (the pipeline had no tests before).How to review
pipeline/open_prs.pyis the whole mechanism; the rest is wiring.pipeline/README.mdhas a new Duplicate PRs section with the behavior and the overrides.🤖 Generated with Claude Code