runner install: --repo is ignored when a runner is already registered — silently restarts on the OLD repo and reports success #13

Closed
opened 2026-07-13 14:40:56 +00:00 by dan-claude-bot · 0 comments
dan-claude-bot commented 2026-07-13 14:40:56 +00:00 (Migrated from github.com)

rig runner install --repo <B> on a box already registered to repo A silently skips registration, starts the service still pointed at A, and reports success.

What happens

commands/runner-install.sh treats the mere existence of .runner as "already registered", without checking which repo that file names:

# --- registration token — only when registration is actually pending -------
if [ -e "$RUNNER_DIR/.runner" ]; then
  REG_PENDING=0        # ← doesn't even PROMPT for a token
fi
...
# --- configure ---
if [ -e "$RUNNER_DIR/.runner" ]; then
  log "already registered; skipping configure"    # ← --repo is never compared
else
  ... config.sh --url "https://github.com/${REPO}" ...
fi
...
(cd "$RUNNER_DIR" && ./svc.sh install "$RUNNER_USER")
(cd "$RUNNER_DIR" && ./svc.sh start)
log "runner ${RUNNER_NAME} (labels: ${LABELS}) installed and running"

So --repo is accepted, validated, and then ignored. The box comes back up on the old repo and the last line says it worked.

There is no signal to the operator that anything is wrong: because REG_PENDING=0, it doesn't even ask for the registration token they just minted for the new repo. The command that looks like it did the least (no prompt, fast) is the one that did the wrong thing.

Why it bites

This is the natural next command after a failed or partial repoint. Concretely, ours:

  1. rig runner repoint --repo <new> aborted at deregistration (expired removal token — GitHub answers a bad token on POST /actions/runner-registration with a bare 404, which reads as "runner not found" and sends you looking in the wrong place).
  2. repoint had already uninstalled the service — correctly, service-first — so the box was left with .runner still naming the old repo and no service.
  3. The obvious recovery is rig runner install --repo <new>. That would have silently restarted the runner on the old repo, printed "installed and running", and left the new repo with zero runners — so its runs-on: [self-hosted, …] jobs queue forever against a runner that will never come.

The failure is worse than a no-op. Moving a runner between repos is usually a trust-boundary act — you are taking a machine off a repo for a reason. A tool that quietly puts it back, and says success, defeats the whole point of the move.

Fix

commands/runner-status.sh already solves the hard part — it parses .runner (JSON) with a dependency-free helper, because a rig-bootstrapped box has no jq:

json_field() {
  grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$1" \
    | head -n1 | sed 's/.*:[[:space:]]*"//; s/"$//'
}
REPO_URL="$(json_field "$RUNNER_DIR/.runner" gitHubUrl)"

Lift that into a shared helper (or duplicate the four lines) and gate install on it:

if [ -e "$RUNNER_DIR/.runner" ]; then
  CURRENT="$(json_field "$RUNNER_DIR/.runner" gitHubUrl)"
  WANTED="https://github.com/${REPO}"
  if [ "$CURRENT" != "$WANTED" ]; then
    die "this box is already registered to ${CURRENT}, not ${WANTED}.
Run 'rig runner remove' (or 'rig runner remove --local') first, or use
'rig runner repoint --repo ${REPO}' to move it in one act."
  fi
fi

This preserves convergence, which is the property worth keeping. Re-running install against the repo the box is already on stays a clean exit-0 no-op — that is real convergence. Skipping when the repo differs is not convergence, it is silently ignoring the argument.

Suggested test

test/ gains a case: a box with a .runner naming repo A, install --repo Bexits non-zero, does not start a service, and the message names both repos.

Same "convergent by skipping" root cause the runner-lifecycle work already fixed in repoint; install was left behind. Worth a skim of the other commands for the same shape — a guard that proves something is installed, but never that it is the thing the flags asked for.

`rig runner install --repo <B>` on a box already registered to repo **A** silently skips registration, starts the service **still pointed at A**, and reports success. ## What happens `commands/runner-install.sh` treats the mere existence of `.runner` as "already registered", without checking *which* repo that file names: ```sh # --- registration token — only when registration is actually pending ------- if [ -e "$RUNNER_DIR/.runner" ]; then REG_PENDING=0 # ← doesn't even PROMPT for a token fi ... # --- configure --- if [ -e "$RUNNER_DIR/.runner" ]; then log "already registered; skipping configure" # ← --repo is never compared else ... config.sh --url "https://github.com/${REPO}" ... fi ... (cd "$RUNNER_DIR" && ./svc.sh install "$RUNNER_USER") (cd "$RUNNER_DIR" && ./svc.sh start) log "runner ${RUNNER_NAME} (labels: ${LABELS}) installed and running" ``` So `--repo` is accepted, validated, and then **ignored**. The box comes back up on the old repo and the last line says it worked. There is no signal to the operator that anything is wrong: because `REG_PENDING=0`, it doesn't even ask for the registration token they just minted for the new repo. The command that *looks* like it did the least (no prompt, fast) is the one that did the wrong thing. ## Why it bites This is the natural next command after a failed or partial `repoint`. Concretely, ours: 1. `rig runner repoint --repo <new>` aborted at deregistration (expired removal token — GitHub answers a bad token on `POST /actions/runner-registration` with a bare **404**, which reads as "runner not found" and sends you looking in the wrong place). 2. `repoint` had already uninstalled the service — correctly, service-first — so the box was left with `.runner` still naming the **old** repo and no service. 3. The obvious recovery is `rig runner install --repo <new>`. That would have **silently restarted the runner on the old repo**, printed "installed and running", and left the new repo with zero runners — so its `runs-on: [self-hosted, …]` jobs queue forever against a runner that will never come. The failure is worse than a no-op. Moving a runner between repos is usually a **trust-boundary** act — you are taking a machine *off* a repo for a reason. A tool that quietly puts it back, and says success, defeats the whole point of the move. ## Fix `commands/runner-status.sh` already solves the hard part — it parses `.runner` (JSON) with a dependency-free helper, because a rig-bootstrapped box has no `jq`: ```sh json_field() { grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$1" \ | head -n1 | sed 's/.*:[[:space:]]*"//; s/"$//' } REPO_URL="$(json_field "$RUNNER_DIR/.runner" gitHubUrl)" ``` Lift that into a shared helper (or duplicate the four lines) and gate `install` on it: ```sh if [ -e "$RUNNER_DIR/.runner" ]; then CURRENT="$(json_field "$RUNNER_DIR/.runner" gitHubUrl)" WANTED="https://github.com/${REPO}" if [ "$CURRENT" != "$WANTED" ]; then die "this box is already registered to ${CURRENT}, not ${WANTED}. Run 'rig runner remove' (or 'rig runner remove --local') first, or use 'rig runner repoint --repo ${REPO}' to move it in one act." fi fi ``` **This preserves convergence, which is the property worth keeping.** Re-running `install` against the repo the box is *already* on stays a clean exit-0 no-op — that is real convergence. Skipping when the repo *differs* is not convergence, it is silently ignoring the argument. ## Suggested test `test/` gains a case: a box with a `.runner` naming repo A, `install --repo B` → **exits non-zero**, does not start a service, and the message names both repos. ## Related Same "convergent by skipping" root cause the runner-lifecycle work already fixed in `repoint`; `install` was left behind. Worth a skim of the other commands for the same shape — a guard that proves *something* is installed, but never that it is the thing the flags asked for.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/rig#13
No description provided.