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/cli on that machine and restart pags 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/cli on your machine and restart pags 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.
runnerTooOld is called from a handler that has already resolved the connection (resolveTarget → getBoundRunnerConn), and RunnerConn carries runnerNode. 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). Run npm i -g @proagentstore/clion **Sergeys-Mac-mini.local** and restartpags up there.
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.runnerNode is 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/nodes already computes every node's runnerVersion. When another of the owner's connected nodes satisfies REPO_SEARCH_MIN_CLI, name it: "RLs-MacBook-Air is 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_CLI in repo-policy-act.ts is the same pattern and will read the same way. One helper — "which machine, running what, needs what" — serves both.
Alternatives rejected
Acceptance
- With an instance pinned to a node whose
runner_version < REPO_SEARCH_MIN_CLI, repo_find/repo_grep return 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
repoMissingMessage already 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).
What the owner saw
Three times in 40 minutes, on the same agent:
and the agent relayed it faithfully:
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:machineIdRLs-MacBook-Air3901c9c4-…Mac,RLs-MacBook-Air.localSergeys-Mac-mini.local8de74159-…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 readsconnected: 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) — sorepo_findandrepo_grepare exactly the two tools the pinned machine cannot serve. Its other repo tools work: at 04:03:55 the same instance ranrepo_read_fileandrepo_gitsuccessfully 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: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:The comment above
REPO_SEARCH_MIN_CLIgets 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
runnerTooOldis called from a handler that has already resolved the connection (resolveTarget→getBoundRunnerConn), andRunnerConncarriesrunnerNode. Thread it in: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.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.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
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.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
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.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_grepandREPO_SEARCH_MIN_CLI), #513 and #517 (remedies the reader cannot execute), #379 / #461 (the pinned-machine diagnosis this should reuse).