The token that cannot see the other repository

A cross-repository step that does nothing is rarely loud. It usually fails as an authentication symptom wearing a not-found costume, and on this fleet there is a specific token that cannot see the other repository — by design, because it is scoped to one repo.

The step that has to reach across

ymrtech/nix-config-private deploys by bumping its own input inside the public ymrtech/nix-config repository’s flake.lock. Pushing to the private repo alone changes nothing on any host until that lock moves. So the private repo’s CD chain has to write a branch and open a pull request in a repository it does not own — and the workflow token cannot do it. The workflow says so in its own env block:

1
2
3
4
5
6
env:
  NIXCFG: ymrtech/nix-config
  API: https://git.ymrtech.com/api/v1
  # github.token is repo-scoped (cannot write nix-config); use the runner
  # user's ~/.netrc (hermes PAT, rendered on every runner host by the public
  # repo's modules/clientsv/nix-netrc.nix).

Two facts follow, and both are load-bearing.

A repo-scoped token answers 404 for a repository outside its scope. Not 403. The API returns the same status it would for a repository that does not exist, so “you are not allowed” and “there is nothing there” are indistinguishable in the log. This is not one endpoint’s quirk: on the same forge, the same netrc token produces nix-config -> 200 ("private":false) and nix-config-private -> 404 (denied). Private-repo run pages and job logs behave the same way — token auth covers the public repo’s pages only.

netrc matches by host. The credential file on this runner has exactly one entry: machine git.ymrtech.com. A lookup with curl -n only finds credentials when the URL’s hostname matches, which is why the CI comment is explicit about it — curl’s netrc lookup matches the HOSTNAME, so you always call the public hostname the netrc knows, never an internal address for the same service.

Why it can exit zero and change nothing

Together those two give you a script with a working read path and a dead write path — the worst combination to debug, because the first half looks like proof that the credentials are fine.

So the step runs, prints something plausible, exits zero, and changes nothing. The only symptom is the absence of an effect.

The fix is to assert the effect

Not to widen the token. A token that can write to everything removes the symptom and keeps the bug — the step was never verified, it just stopped failing. Scope the token to what the chain needs, then check the result directly.

This chain does, because it learned the hard way. Its merge step re-reads the artifact after merging and compares it against the exact revision this run pushed:

1
FATAL: PR #$LOCK_PR reported merged but main's flake.lock does not pin clientsv@$MYSHA

The comment above it records the lesson: a 200 from the merge endpoint without the merge applying was reported as success, because the wait that followed matched a pre-existing deploy of the old main head — the lock never reached main and nothing noticed. A status code from the command that claims to have done the work is not the work. The pull-request check follows the same rule: rather than trusting the POST that created it, it lists open pull requests and matches .head.ref against the per-revision branch name it just pushed.

The general form: assert that the pull request exists at the expected head revision, or that the file at ref=main contains the value you set, or that the branch resolves. Something that can only be true if the effect happened.

What you can check

This is the same class of problem as a green deploy that deployed nothing: a status is an input to a decision, not the decision. That post is here; the layer underneath — the hosts, the runner, and the health gate that verifies a deploy after the fact — is on how the stack is built.

󰔟 Running the same kind of stack?
The tunnel this site is written from is the one I sell: WireGuard to a gateway in Montréal, three DNS postures, one price — and the same "check it yourself" habit as everything above.
[ CREATE ACCOUNT ] [ WHAT IS THE VPN ]
󰣨 ymrtech@ymrtech | 󰌠 NixOS | 󰍢 UTF-8