Repository navigation
"Run npm i -g @proagentstore/cli on that machine" names no machine — the owner has two runners, already upgraded one, and the agent is pinned to the other #524
Description
What the owner saw
Three times in 40 minutes, on the same agent:
2026-08-12 03:23:28 ❌ repo_find This machine's runner is too old to search this repository — it needs CLI 0.4.49 or newer. Run
npm i -g @proagentstore/clion that machine and restartpags up.
2026-08-12 04:03:31 ❌ repo_grep (same) — twice.
and the agent relayed it faithfully:
The search tools are unavailable right now — the local runner needs to be updated before I can read the code. The error says to run
npm i -g @proagentstore/clion your machine and restartpags up.
He has two machines connected, and he has already upgraded one of them to 0.4.49. The message cannot tell him which machine it means, and nothing on the surface tells him this agent is pinned to the other one. Following the instruction on the obvious machine — the one he is sitting at — changes nothing.
Measured, live
GET /v1/terminals/nodes, 2026-08-12 04:16 UTC, both connected:
| node | machineId |
runner | aka |
|---|---|---|---|
RLs-MacBook-Air |
3901c9c4-… |
0.4.49 | Mac, RLs-MacBook-Air.local |
Sergeys-Mac-mini.local |
8de74159-… |
0.4.45 | — |
Instance bd43f4de-ef35-4051-bdec-43f8571414a1 ("Chess coder 2"), config.runnerNode = "Sergeys-Mac-mini.local" — the 0.4.45 machine. On that node the instance reads connected: true, bound: true; the 0.4.49 machine holds live sockets for 15 of the owner's other instances.
REPO_SEARCH_MIN_CLI = "0.4.49" (workers/api/src/lib/connectors/repo-local.ts:53) — so repo_find and repo_grep are exactly the two tools the pinned machine cannot serve. Its other repo tools work: at 04:03:55 the same instance ran repo_read_file and repo_git successfully against the same checkout.
Mechanism — two correct decisions composing into an unactionable message
1. The pin is authoritative and deliberately never falls through. workers/api/src/lib/runner-client.ts:74-100, docstring and code:
"When pinned, that machine is authoritative: we do NOT silently fall back to another node when it's offline — doing so would run the agent somewhere the user didn't choose and misreport 'online' for the wrong machine."
That is right, and #379/#380/#461/#500 are the trail of bugs from getting it wrong. It is not the defect.
2. The remedy names no machine. workers/api/src/lib/connectors/repo-local.ts:57-61:
function runnerTooOld(e: unknown, what: string): string | null {
const message = e instanceof Error ? e.message : String(e);
if (!/→ 404|not found/i.test(message)) return null;
return `This machine's runner is too old to ${what} — it needs CLI ${REPO_SEARCH_MIN_CLI} or newer. Run \`npm i -g @proagentstore/cli\` on that machine and restart \`pags up\`.`;
}The comment above REPO_SEARCH_MIN_CLI gets the principle exactly right — "Named, not 'update the CLI' — a version without a number is one somebody has to go and find" — and then stops one step short: the version is named, the machine is not, and the machine is the part the reader has to go and find. Worse, "that machine" is a phrase the reader will resolve to the machine in front of them, which here is the 0.4.49 one that needs nothing.
This is the #513 / #517 class: a remedy the reader cannot execute. #517 fixed the case where the remedy was discarded; this is the case where it is relayed intact and is still not actionable.
What to do, cheapest first
- Name the machine, and its version.
runnerTooOldis called from a handler that has already resolved the connection (resolveTarget→getBoundRunnerConn), andRunnerConncarriesrunnerNode. Thread it in:The runner on **Sergeys-Mac-mini.local** is too old to search this repository (it has 0.4.45; this needs 0.4.49 or newer). Runnpm i -g @proagentstore/clion **Sergeys-Mac-mini.local** and restartpags upthere.
One parameter, one call site, and it is the whole difference between a message the owner can act on and one he cannot. - Say the pin is why it is that machine. When
config.runnerNodeis set, append: "This agent is pinned to that machine (Settings → Runs on), so it will not use another one." Without this the owner has no way to discover the pin from the failure — and the pin is the only reason the upgraded machine is not being used. - Say when a capable machine exists.
GET /v1/terminals/nodesalready computes every node'srunnerVersion. When another of the owner's connected nodes satisfiesREPO_SEARCH_MIN_CLI, name it: "RLs-MacBook-Airis connected and already runs 0.4.49 — repin this agent there, or upgrade the pinned machine." That turns a dead end into a choice. - Generalise it.
SWITCH_BRANCH_MIN_CLIinrepo-policy-act.tsis the same pattern and will read the same way. One helper — "which machine, running what, needs what" — serves both.
Alternatives rejected
- Fall back to a capable node when the pinned one is too old. This breaks the pin's contract at
runner-client.ts:74-100deliberately: a Repo Coder's checkout is on a specific machine, and searching a different machine's copy would return confidently wrong results — a worse failure than an honest refusal. [bug] A machine's identity is os.hostname(), so one laptop is three machines — and a pin to a name it no longer uses answers every call with "run pags up" while pags up is running #379's alias path is the only sanctioned relaxation, and it is identity-based (samemachine_id), not capability-based. - Auto-upgrade the runner. Out of scope and the CLI is a user-installed global package (see CLAUDE.md § Distribution); the platform cannot install software on someone's laptop.
- Only bump
REPO_SEARCH_MIN_CLIhandling in the agent prompt. The sentence the agent relays is already the connector's own (that is [bug] A refused tool's own remedy is discarded and the agent invents "Settings → Connections" — reproduced 5/5, and the transcript truncates the real remedy away #517's fix, and it worked here — the agent quoted it accurately). The defect is in the sentence.
Acceptance
- With an instance pinned to a node whose
runner_version < REPO_SEARCH_MIN_CLI,repo_find/repo_grepreturn a message that contains the node name, its actual version, the required version, and — when the pin is set — that the agent is pinned there. - When another connected node meets the requirement, the message names it.
- Unit test on the message builder (pure, as
repoMissingMessagealready is) covering: pinned + no capable alternative; pinned + capable alternative present; unpinned.
Regression risk
The message is relayed verbatim into chat and is now longer; #517's own finding was that a long remedy gets truncated away before the owner sees it. Whatever cap applies to a tool-result line must keep the machine name — put it in the first clause, not after the version numbers. Test that the truncated form still names the machine.
Related: #508 (added repo_find/repo_grep and REPO_SEARCH_MIN_CLI), #513 and #517 (remedies the reader cannot execute), #379 / #461 (the pinned-machine diagnosis this should reuse).
Activity
serge-ivo commentedon Aug 12, 2026
Line-number correction (the file is being edited concurrently):
REPO_SEARCH_MIN_CLI = "0.4.49"isworkers/api/src/lib/connectors/repo-local.ts:54, not :53. TherunnerTooOldmessage is at :57-61 (the template string itself on :60), re-checked againstb590eee.fix(mcp): the "no credential" refusal asks the server how to get one,…
fix(agents): a runner-too-old refusal names the machine, its version,…
serge-ivo commentedon Aug 15, 2026
Shipped in
1494244. All three acceptance criteria met; closing.What the message says now
Before:
After, for the account in this issue:
The pin is not relaxed. Your rejected alternative was the right call and
runner-client.ts:74-100still refuses to fall through: a checkout lives on one machine, and searching a different machine's copy returns confidently wrong results — worse than an honest refusal. What changed is that the refusal explains itself.The three criteria
runtimeConnectivity, which already computed every one of them fordiagnoseAttachment. Nothing new is measured.Two judgement calls worth stating
"Connected" is only said when the relay says so. A candidate machine is called connected only if it holds a live socket for this instance right now.
statusis never cleared on disconnect (#238), so a laptop shut months ago still readsregistered— offering that as "connected and ready" would be this exact bug one machine over. A registration we cannot confirm reads "last reported 0.4.51" instead. Same reasoncliAtLeasttreats an unreported version as not capable: naming a machine that turns out to be older converts a dead end into a wild goose chase.The machine name is in the first clause, asserted at an 80-character cut. That is your regression note taken literally — this message is longer than the one it replaces and #517's finding was that a long remedy gets truncated before you see it.
AC4 — generalised
SWITCH_BRANCH_MIN_CLIhad independently written the same machine-less remedy ("this machine's runner has no switch-branch endpoint").classifyRunnerErrornow takes the node — whichrunRepoPolicyRemediationalready holds onconn, so it costs no query — and builds its clause from the same module. Same rule, different grammar: that one is spliced into a work card's prose, so it is a fragment rather than a sentence. Both asserted.The diagnosis never throws: a failed read degrades to the original sentence rather than turning a tool refusal into a 500.
Note on scope
repo-local.tswas being edited concurrently by another session, so the change there is three lines (the call site and the import) and all the substance is in the newlib/runner-upgrade.ts.Verified
pnpm typecheck(11 projects), 9,156 unit tests, biome--error-on-warnings, all six script gates,check-migrations --require-history,docs:drift, bothtsconfig.test.jsongates. CI green on1494244; API and Host workers deployed.Not verified live, and I am naming it rather than implying otherwise: reproducing it needs a machine on a runner older than 0.4.49 with an instance pinned to it, and both your machines are now past that. The message builder is pure and fully asserted, and the facts it reads come from
runtimeConnectivity, which the console's own runner banner already depends on — but norepo_findhas actually returned this string in production. If you ever see it, it should name the Mac mini.fix(coding): the offline banner says WHICH machine, because a boolean…
feat(coding): a run does not build on a base it could not confirm (#801)