Context
openRepoDocPullRequest (src/github/repo-doc-pr.ts) is the single code path that opens/refreshes the automated AGENTS.md/CLAUDE.md (and optional skill-file) pull request for a repo — called from both the scheduled sweep (src/queue/processors.ts) and the on-demand MCP tool loopover_refresh_repo_docs (src/mcp/server.ts, backed by src/github/repo-doc-refresh-runner.ts's performRepoDocRefresh).
Every run always targets the same fixed branch name, loopover/repo-docs (REPO_DOC_BRANCH_NAME, src/github/repo-doc-pr.ts:42) — this is deliberate, per the file's own header comment: "Stable across runs (not per-run unique) so a repeat invocation targets the SAME branch/PR instead of piling up duplicates."
The function only checks for an open PR on that branch before deciding whether to create a new one (src/github/repo-doc-pr.ts:167, state: "open"). If none is found, it always creates a brand-new ref via:
// src/github/repo-doc-pr.ts:227
await octokit.request("POST /repos/{owner}/{repo}/git/refs", { owner, repo, ref: `refs/heads/${REPO_DOC_BRANCH_NAME}`, sha: commitSha });
POST /git/refs fails with a 422 ("Reference already exists") if the ref is already present. This happens whenever a maintainer closes the repo-doc PR without deleting the branch (GitHub's UI offers a "Delete branch" button on a closed PR, but nothing forces it — and a PR closed via the API/a bot doesn't delete the branch at all). At that point:
- The
state: "open" PR lookup at line 167 finds nothing (the PR is closed).
- The function proceeds through the diff-aware refresh logic and decides a new commit is needed.
- The
POST /git/refs call at line 227 throws (branch already exists).
- The whole function is wrapped in one outer
try { ... } catch (error) { return { opened: false, reason: error.message } } (src/github/repo-doc-pr.ts:141-244), so the failure is swallowed into a generic { opened: false, reason } result — no exception surfaces, no distinct signal from "generation legitimately skipped" (e.g. .loopover.yml disabled it).
performRepoDocRefresh (src/github/repo-doc-refresh-runner.ts:54-62) records the attempt regardless of outcome, so the scheduled sweep won't retry sooner — but every future attempt (scheduled or manual) hits the exact same 422 and fails the exact same way, permanently, until an operator manually deletes the orphaned branch out-of-band.
Nothing in this codepath ever checks whether the branch already exists before creating it, and nothing ever deletes/updates it — confirmed via grep -rn "loopover/repo-docs\|REPO_DOC_BRANCH_NAME" src/ returning only the three lines above (no delete, no PATCH-ref call, no existence probe).
Requirements
openRepoDocPullRequest must tolerate the target branch (refs/heads/loopover/repo-docs) already existing with no open PR on it (i.e., a previous PR was closed without deleting the branch), and still successfully commit the refreshed content and open a fresh PR.
- The fix must use GitHub's ref-update primitive (
PATCH /repos/{owner}/{repo}/git/refs/{ref} with sha + force: true) to update the existing ref in place when the create call reports the ref already exists, rather than failing the whole operation. Since this branch is exclusively owned by this feature (never shared with contributor work — see the file's own header comment), force-updating it to the freshly built commit is safe.
- Do not change the existing "only reuse an open PR" behavior (
src/github/repo-doc-pr.ts:167) — a closed PR on this branch must still result in a new PR being opened (not silently updating a closed one), only the underlying branch ref must be reusable.
- Preserve the existing fail-safe contract: any other GitHub API failure during this recovery path must still degrade to
{ opened: false, reason }, never throw out of openRepoDocPullRequest.
Deliverables
Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, on the changed lines in src/github/repo-doc-pr.ts — both the "create succeeds" (existing, already-covered) branch and the new "create fails with ref-exists, fall back to PATCH" branch must be exercised. Also add a case where the create call fails for a genuinely different reason (e.g. a 500) to confirm the existing fail-safe { opened: false, reason } behavior is untouched for non-"already exists" failures.
Expected Outcome
A repo whose loopover/repo-docs PR was closed without deleting the branch resumes getting successful AGENTS.md/CLAUDE.md refreshes on the next scheduled sweep or manual loopover_refresh_repo_docs call, instead of being permanently stuck failing the same 422 forever.
Links & Resources
Context
openRepoDocPullRequest(src/github/repo-doc-pr.ts) is the single code path that opens/refreshes the automatedAGENTS.md/CLAUDE.md(and optional skill-file) pull request for a repo — called from both the scheduled sweep (src/queue/processors.ts) and the on-demand MCP toolloopover_refresh_repo_docs(src/mcp/server.ts, backed bysrc/github/repo-doc-refresh-runner.ts'sperformRepoDocRefresh).Every run always targets the same fixed branch name,
loopover/repo-docs(REPO_DOC_BRANCH_NAME,src/github/repo-doc-pr.ts:42) — this is deliberate, per the file's own header comment: "Stable across runs (not per-run unique) so a repeat invocation targets the SAME branch/PR instead of piling up duplicates."The function only checks for an open PR on that branch before deciding whether to create a new one (
src/github/repo-doc-pr.ts:167,state: "open"). If none is found, it always creates a brand-new ref via:POST /git/refsfails with a 422 ("Reference already exists") if the ref is already present. This happens whenever a maintainer closes the repo-doc PR without deleting the branch (GitHub's UI offers a "Delete branch" button on a closed PR, but nothing forces it — and a PR closed via the API/a bot doesn't delete the branch at all). At that point:state: "open"PR lookup at line 167 finds nothing (the PR is closed).POST /git/refscall at line 227 throws (branch already exists).try { ... } catch (error) { return { opened: false, reason: error.message } }(src/github/repo-doc-pr.ts:141-244), so the failure is swallowed into a generic{ opened: false, reason }result — no exception surfaces, no distinct signal from "generation legitimately skipped" (e.g..loopover.ymldisabled it).performRepoDocRefresh(src/github/repo-doc-refresh-runner.ts:54-62) records the attempt regardless of outcome, so the scheduled sweep won't retry sooner — but every future attempt (scheduled or manual) hits the exact same 422 and fails the exact same way, permanently, until an operator manually deletes the orphaned branch out-of-band.Nothing in this codepath ever checks whether the branch already exists before creating it, and nothing ever deletes/updates it — confirmed via
grep -rn "loopover/repo-docs\|REPO_DOC_BRANCH_NAME" src/returning only the three lines above (no delete, no PATCH-ref call, no existence probe).Requirements
openRepoDocPullRequestmust tolerate the target branch (refs/heads/loopover/repo-docs) already existing with no open PR on it (i.e., a previous PR was closed without deleting the branch), and still successfully commit the refreshed content and open a fresh PR.PATCH /repos/{owner}/{repo}/git/refs/{ref}withsha+force: true) to update the existing ref in place when the create call reports the ref already exists, rather than failing the whole operation. Since this branch is exclusively owned by this feature (never shared with contributor work — see the file's own header comment), force-updating it to the freshly built commit is safe.src/github/repo-doc-pr.ts:167) — a closed PR on this branch must still result in a new PR being opened (not silently updating a closed one), only the underlying branch ref must be reusable.{ opened: false, reason }, never throw out ofopenRepoDocPullRequest.Deliverables
src/github/repo-doc-pr.ts: when thePOST /git/refscreate call fails because the ref already exists, fall back toPATCH /repos/{owner}/{repo}/git/refs/heads/{REPO_DOC_BRANCH_NAME}with{ sha: commitSha, force: true }to update it in place, then proceed to open the PR exactly as the create-succeeded path already does.test/unit/repo-doc-pr.test.tsthat mocksPOST /git/refsreturning a 422 "Reference already exists" response and assertsopenRepoDocPullRequeststill returns{ opened: true, reused: false, ... }with a real PR opened against the force-updated branch (mirroring the existing test file's mocking style for the other GitHub calls in this path).Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, on the changed lines in
src/github/repo-doc-pr.ts— both the "create succeeds" (existing, already-covered) branch and the new "create fails with ref-exists, fall back to PATCH" branch must be exercised. Also add a case where the create call fails for a genuinely different reason (e.g. a 500) to confirm the existing fail-safe{ opened: false, reason }behavior is untouched for non-"already exists" failures.Expected Outcome
A repo whose
loopover/repo-docsPR was closed without deleting the branch resumes getting successful AGENTS.md/CLAUDE.md refreshes on the next scheduled sweep or manualloopover_refresh_repo_docscall, instead of being permanently stuck failing the same 422 forever.Links & Resources
src/github/repo-doc-pr.ts(the file to change, seeopenRepoDocPullRequestandbuildRepoDocTree/REPO_DOC_BRANCH_NAMEnear the top)src/github/repo-doc-refresh-runner.ts(both callers of this function: the scheduled sweep path and the on-demand MCP path)test/unit/repo-doc-pr.test.ts(existing test file to extend)