Skip to content

shim: guard PEERAGENT_BIN against self-exec loop (pointing at a shim) - #2

Merged
nklisch merged 1 commit into
mainfrom
shim/peeragent-bin-self-exec-guard
Jun 20, 2026
Merged

shim: guard PEERAGENT_BIN against self-exec loop (pointing at a shim)#2
nklisch merged 1 commit into
mainfrom
shim/peeragent-bin-self-exec-guard

Conversation

@nklisch

@nklisch nklisch commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Problem

PEERAGENT_BIN is meant to override the resolved Go binary. If set to a shim copy (this script, or plugin/bin/peeragent), step 1 runs:

exec "$PEERAGENT_BIN" "$@"

→ the shim re-enters step 1 with the same env var → exec itself → infinite self-exec loop. It burns a core and never starts the agent. No error, no exit.

Spotted in the wild this session: an opus peer-review job reported "running" for 19 minutes while a bare sh process sat at 53% CPU with no child — the review never actually executed. Easy footgun: the natural but wrong instinct is PEERAGENT_BIN=$(which peeragent) pointing at the shim.

Fix

In step 1, if the PEERAGENT_BIN target's first two bytes are #! (a shell shim — the Go binaries start with ELF/Mach-O magic), fail fast with exit 2 and a clear message instead of spinning.

Catches pointing at any shim copy (root bin/ or plugin/bin/), not just the exact self path.

--text is detected inline because wants_text is defined later in the script and sh does not parse functions ahead (verified: calling a later-defined function from step 1 gives command not found).

Verified

Case Result
PEERAGENT_BIN → shim itself (the bug) exit 2, clear JSON error, no spin
PEERAGENT_BIN → other shim copy (plugin/bin/peeragent) exit 2
PEERAGENT_BIN → real Go binary unaffected, execs normally
PEERAGENT_BIN unset → auto-detect unaffected
--text mode human-readable message
no args guard fires correctly under set -eu

Test

Added a "shim PEERAGENT_BIN self-exec-loop guard" step to scripts/validate.sh — runs the guarded path under timeout 3 so a regression (guard removed/broken) hangs and fails the suite rather than passing by luck. Asserts process exit 2 (set by the shim itself, independent of the Go binary's exit-code semantics) and the self-exec loop message.

plugin/bin/peeragent re-synced from bin/peeragent via package-plugin.sh.

Out of scope (flagged, not fixed here)

The pre-existing "shim smoke" step asserts process exit 4 for --status missing-job, but on this build the Go binary's process exit is 1 (the exit_code:4 lives only in the JSON metadata). That step appears already red on clean upstream, unrelated to this change. Worth a separate look at how the Go binary maps failure status → process exit code.

…xec loop)

PEERAGENT_BIN is meant to override the resolved Go binary. If a user sets it to
a shim copy (this script, or plugin/bin/peeragent), step 1 does
`exec "$PEERAGENT_BIN" "$@"` -> the shim re-runs step 1 with the same env var ->
exec itself -> forever. Symptom: a `sh` process pinning a core with no child,
spotted in the wild this session (an opus peer-review job that "ran" for 19min
without ever starting the agent).

Guard: in step 1, if the PEERAGENT_BIN target's first two bytes are `#!` (a
shell shim; the Go binaries start with ELF/Mach-O magic), fail fast with exit 2
and a clear message instead of spinning. Catches pointing at ANY shim copy
(root bin/ or plugin/bin/), not just the exact self path. A valid override to
the Go binary and the unset/auto-detect path are unaffected (verified: shim->self
and shim->plugin-shim now exit 2; shim->Go-binary and unset both still work).

The --text flag is detected inline (wants_text is defined later in the script
and sh does not parse functions ahead, so it can't be called from step 1).

Re-synced plugin/bin/peeragent from bin/peeragent via package-plugin.sh.

Test: added a "shim PEERAGENT_BIN self-exec-loop guard" step to
scripts/validate.sh — runs the guarded path under `timeout 3` so a regression
(hang/loop) fails the suite rather than passing by luck. Asserts process exit 2
(set by the shim, independent of the Go binary's exit-code semantics) and the
"self-exec loop" message.

Note (out of scope, flagged): the pre-existing "shim smoke" step asserts
process exit 4 for `--status missing-job`, but the Go binary's process exit is 1
(the `exit_code:4` lives only in the JSON metadata) on this build — that step
appears already red on clean upstream, unrelated to this change.
@nklisch
nklisch merged commit ee6f80c into main Jun 20, 2026
1 check passed
@nklisch
nklisch deleted the shim/peeragent-bin-self-exec-guard branch June 20, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant