Summary
The operator-managed self-managed bundle refuses to start when a consumer sets PI_BIN to its own wrapper script, failing before Pi is spawned with a 54-byte stderr:
Managed Needlefish rejected an unapproved Pi launcher
This blocks the pi runner lane for SaiensCreditSystemRails and currently has no in-consumer workaround.
Where the check lives
The rejection string is not in this repository at any ref, and not in the consumer repository:
git grep unapproved over all local/remote refs of this repo: no match.
- Same search over
frankekn/SaiensCreditSystemRails at origin/main: no match.
- Upstream launcher resolution is
src/shared/codex.ts:1198 -> command: process.env.PI_BIN ?? "pi", with no launcher-identity or allowlist check. RUNNER_ENV_ALLOWLIST.pi (src/shared/codex.ts:83-100) already passes PI_BIN through.
So the check is introduced by the operator-provided self-managed.patch that ships inside the installed release, which is provisioned by hand per README ("Provision the tested self-managed bundle as the runner service account under ~/.local/share/needlefish-self/releases/<self_version>"). Because installation is managed outside CI, the authoritative bytes exist only on the runner and are not under version control.
Reproduction
Consumer: frankekn/SaiensCreditSystemRails, runner label saiens-needlefish-local-proxy (ubuntu-saiens-deploy-x64-1).
Failing gate: the review job step Verify read-only repository evidence contract, which runs node --test policy/.github/scripts/needlefish-repo-integration.test.mjs. That test spawns the installed reviewer binary with PI_BIN pointing at the consumer's wrapper.
Exact runs:
| Run |
Head |
Result |
34806706871 |
ad9f736a90e19a54356e2c819befa2f8fcae83d2 (PR #1188) |
Reviewer failed; stderr bytes 54 |
34810534625 |
6eea75ed9cfd1c05fa73724e15612c5716d9a2b9 (PR #1210) |
Reviewer failed; stderr bytes 54; stderr="Managed Needlefish rejected an unapproved Pi launcher\n" |
Why the consumer wraps the launcher
The consumer's .github/scripts/needlefish-pi wrapper exists to pin the review policy: it rejects unexpected argv, requires its own runner session before credentials are reachable, caps temp file size, and strips NODE_OPTIONS/NODE_PATH. Pointing PI_BIN at the raw pi CLI would drop those guarantees.
Attempted in-consumer fixes (all insufficient)
PR #1210 tried adding PI_BIN to NEEDLEFISH_RUNNER_ENV_PASSTHROUGH, a setsid --wait session preflight, and wrapping the reviewer in setsid --wait. The rejection is unchanged, because it happens inside the bundle before spawn.
Ask
Define and document the approved-launcher contract for the self-managed tier:
- State what makes a launcher approved (path, digest, ownership, or an explicit opt-in env/flag).
- Let a consumer supply a wrapper that satisfies it, without weakening approved-launcher bytes, session ownership, credential boundaries, or fail-closed evidence.
- Ship a regression that covers a wrapper
PI_BIN, and publish a verifiable release/base SHA plus the matching release.json digests so operators can roll it out.
Please keep the failure fail-closed; the current behaviour is correct in spirit, it just has no approved path for a consumer wrapper.
Summary
The operator-managed self-managed bundle refuses to start when a consumer sets
PI_BINto its own wrapper script, failing before Pi is spawned with a 54-byte stderr:This blocks the
pirunner lane for SaiensCreditSystemRails and currently has no in-consumer workaround.Where the check lives
The rejection string is not in this repository at any ref, and not in the consumer repository:
git grep unapprovedover all local/remote refs of this repo: no match.frankekn/SaiensCreditSystemRailsatorigin/main: no match.src/shared/codex.ts:1198->command: process.env.PI_BIN ?? "pi", with no launcher-identity or allowlist check.RUNNER_ENV_ALLOWLIST.pi(src/shared/codex.ts:83-100) already passesPI_BINthrough.So the check is introduced by the operator-provided
self-managed.patchthat ships inside the installed release, which is provisioned by hand per README ("Provision the tested self-managed bundle as the runner service account under~/.local/share/needlefish-self/releases/<self_version>"). Because installation is managed outside CI, the authoritative bytes exist only on the runner and are not under version control.Reproduction
Consumer:
frankekn/SaiensCreditSystemRails, runner labelsaiens-needlefish-local-proxy(ubuntu-saiens-deploy-x64-1).Failing gate: the
reviewjob stepVerify read-only repository evidence contract, which runsnode --test policy/.github/scripts/needlefish-repo-integration.test.mjs. That test spawns the installed reviewer binary withPI_BINpointing at the consumer's wrapper.Exact runs:
34806706871ad9f736a90e19a54356e2c819befa2f8fcae83d2(PR #1188)Reviewer failed; stderr bytes 54348105346256eea75ed9cfd1c05fa73724e15612c5716d9a2b9(PR #1210)Reviewer failed; stderr bytes 54; stderr="Managed Needlefish rejected an unapproved Pi launcher\n"Why the consumer wraps the launcher
The consumer's
.github/scripts/needlefish-piwrapper exists to pin the review policy: it rejects unexpected argv, requires its own runner session before credentials are reachable, caps temp file size, and stripsNODE_OPTIONS/NODE_PATH. PointingPI_BINat the rawpiCLI would drop those guarantees.Attempted in-consumer fixes (all insufficient)
PR #1210 tried adding
PI_BINtoNEEDLEFISH_RUNNER_ENV_PASSTHROUGH, asetsid --waitsession preflight, and wrapping the reviewer insetsid --wait. The rejection is unchanged, because it happens inside the bundle before spawn.Ask
Define and document the approved-launcher contract for the self-managed tier:
PI_BIN, and publish a verifiable release/base SHA plus the matchingrelease.jsondigests so operators can roll it out.Please keep the failure fail-closed; the current behaviour is correct in spirit, it just has no approved path for a consumer wrapper.