# Promote

> Promote a reviewed inspect_ai fork branch upstream — open the UKGovernmentBEIS PR with the fully-qualified Fixes ref, then do the tracking bookkeeping (Atlas Sign-off stage, Upstream PR field, issue comment, supersede the fork PR). Idempotent — also use it to heal the bookkeeping of an already-promoted issue.

- Skill: `meridianlabs-ai/promote` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add meridianlabs-ai/promote`
- Raw SKILL.md: https://api.skillmd.com/api/skills/meridianlabs-ai/promote/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: meridianlabs-ai (https://skillmd.com/u/meridianlabs-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/meridianlabs-ai/promote

---


# Promote fork work upstream

Promote work from `meridianlabs-ai/inspect_ai` (the fork) to
`UKGovernmentBEIS/inspect_ai` (upstream), per the tracking contract in
`meridianlabs-ai/agents` design/atlas-tracking.md → "The fork: promotion and
the terminal sync".

## Fast path (issue number in hand)

Run the script that lives next to this skill (substitute this skill's base
directory). Add `--dry-run` first if the user asked to preview:

```sh
bash <skill-base-dir>/promote.sh <N> [--dry-run]
```

One invocation does everything, each write check-before-write (idempotent —
rerunning heals an already-promoted issue): resolves the fork PR + branch
from the issue's chips; prints preflight ADVISORY lines (review verdict, fork
CI) — **relay these to the user, and pause for confirmation if the verdict
isn't `clean` or CI shows failures**; adopts the existing upstream PR via the
issue's cross-repo chip (the REST `pulls?head=` filter silently returns
[] for org-owned heads — observed on the org-fork pair AND same-repo on
the fork itself — never use it anywhere) or, on the create path, first syncs
the branch with upstream main (server-side merge via the fork-network merges
API — org-fork heads take no maintainer edits and these branches trail the
fork's main mirror, so a fresh promotion usually opens behind; a conflict,
commonly CHANGELOG, aborts with exit 5 BEFORE any upstream PR is opened, for
a human to resolve on the branch) and creates it with the fully-qualified
`Fixes meridianlabs-ai/inspect_ai#N` (bare `#N` refs are rewritten — they
would rebind to upstream's tracker), plus a bare `Fixes #<up>` when the
fork issue was imported from upstream (its `Upstream issue:` body line —
see the import skill; creation-time only, adopted PRs aren't edited); assigns +
requests review from `dragonstyle` on open PRs (the default — `REVIEWER=<login>`
overrides it, see Cautions); sets the board's `Upstream PR` field (the sync's
join key — the #90 lesson), stage → Sign-off + Status → In progress (never
on a CLOSED issue, never downgrading Sign-off/Merge); comments the upstream
link on the fork issue; supersedes and closes the open fork PR.

Exit codes: **0** ok (report the `OK …` line plus which steps were created
vs already present); **3** no fork-PR chip — resolve inputs via the slow
path below, then run the script anyway if a branch emerges (it only needs
the chip for resolution); **4** branch not on the fork; **5** preflight
hard failure (a `REVIEWER` who is provably not a collaborator on upstream or
on the ts-mono companion's repo, or a conflict merging upstream main into the
branch — either way no upstream PR was opened; fix the login / resolve the
conflict on the branch and re-run).

## Slow path (no chip)

From an issue with no linked-PR chip: scan machine-account comments for
`/pull/` refs (take the still-open one). From a PR or branch: the issue
number comes from the branch name (`claude/issue-N-*`), else a same-repo
`Fixes` ref in the PR body. Human-named branches: get the branch from the
fork PR's head. Once resolved, prefer fixing the chip (run the
link-upstream-chips sweep) and re-running the script over hand-executing
its steps.

Multiple chips are normal: an issue can carry its fork PR, a ts-mono
companion, and (after promotion) the upstream PR. Resolution filters by
repo, so extra chips never confuse it. A ts-mono companion (same branch
name, open) additionally gets the SAME reviewer assigned and
review-requested at promotion time — ts-mono has no promotion step of
its own, so this is where the viewer half enters human sign-off.

NEVER wait for GitHub to materialize a missing chip: on the fork, closing
refs (`Fixes #N`) are inert — GitHub only processes them for PRs based on
the default branch, and fork PRs base on `meridian` — so no amount of
editing the PR body or polling produces one (observed: a session polled
5 minutes for a chip that can never appear). Chips on the fork come ONLY
from the link-upstream-chips sweep (`scripts/link-upstream-chips` in the
agents checkout, or `/resolve-board`); run it, or resolve via the slow
path above and proceed.

The script itself runs that sweep as its last step (best-effort: an
expired browser login is reported, never fatal), so a normal promotion
leaves no chips pending — including the new upstream PR's own chip.
If the tail reports the sweep failed on an expired login, recover the
same way resolve-board does — it's the same interactive session: run
`node index.mjs --login` in `scripts/link-upstream-chips`, let the user
complete the GitHub sign-in in the window that opens (good for ~2
weeks), then re-run the sweep. The script deliberately never does this
itself: a promotion must not block on a browser sign-in.

## Report

Upstream PR link, issue link, stage set, and which bookkeeping steps were
created vs already present (healed vs no-op). From here the hourly Atlas
sync owns the tail: approval → Merge, merge → Done (it closes the fork
issue), changes-requested → Review, re-request → Sign-off.

## Design decision: org-fork heads are deliberate

The fork's AGENTS.md prefers personal-fork promotion because GitHub grants
no maintainer-edits on org-fork PR heads. That concern doesn't apply to our
promotions: every upstream maintainer has write access to the meridianlabs
fork itself, so they can update or fix the PR branch directly (decision:
Ransom, 2026-08-12). Keep promoting from the org fork; the script's REST
`head_repo` creation is the required mechanism (GraphQL/`gh pr create`
cannot resolve org-fork heads at all).

## Cautions

- Never push to `main`/`meridian`. Promotion opens a PR from the existing
  branch, and on the create path writes a single merge commit onto that PR
  branch to sync it with upstream main (server-side, never a local checkout);
  it touches no other ref.
- Upstream is not ours: no labels and no Meridian-internal markers on the
  upstream PR beyond the `Fixes` ref. The one exception is the `dragonstyle`
  assignee + review request (explicitly requested by Ransom). Override the
  reviewer for one run with `REVIEWER=<login>` in the environment
  (`REVIEWER=<login> bash <skill-base-dir>/promote.sh <N>`); the default
  stays `dragonstyle`. The override covers the ts-mono companion too (both
  halves deliberately get the same reviewer). Preflight checks an OVERRIDDEN
  login against upstream (the default is known-good and skipped there) and
  any reviewer against ts-mono when a companion exists (exit 5 on a 404,
  before any write). The upstream collaborator lookup needs push access, so
  a token that cannot see it gets a `WARN: could not verify` line and the
  run continues as before — only a 404 is treated as a bad login. The login
  is case-insensitive (lower-cased once; the idempotency checks compare
  case-insensitively too). The script prints the effective reviewer on its
  `ADVISORY:` line.
- Do not merge anything — upstream merges are upstream's call; the fork
  issue closes via the sync when that happens.

