Explicit project GitHub routing

Managed PATHs contain the real gh, not a Cacophony shim. Use caco pr or the explicit shared helper for provider operations; agents and scripts must not call bare gh. Project routing is transport selection, not new repository or mutation authority.

Inspect without credentials

caco pr gh-env --project example
caco pr gh-env --project example --json

The helper prints only the API hostname, gh account login and host-qualified repository. It makes no provider request, reads no credentials and does not change the global active account. Use the target project for a friend repository; the parent agent's account is not a fallback. This command is inspection-only, not a shell credential export.

Run operator scripts with the shared helper

From a compatible reviewed Cacophony checkout:

scripts/pinned-gh.sh --project example -- pr list
scripts/pinned-gh.sh --project example -- api user
python3 scripts/pinned_gh.py --project example -- api user

The shell entrypoint and Python implementation resolve the route with caco pr gh-env. --config PATH selects an explicit configuration source. The route is read with $CACO_BIN first, then with the installed caco on PATH when that is a different binary, so an agent whose CACO_BIN still names an older store path keeps working; the fallback is reported on stderr. A failed route names each caco tried, its version and its own error output. Missing/incompatible caco, invalid routing metadata, missing accounts and failed/empty/timed-out lookup all refuse before the requested command; none select an active account or borrow an ambient token. The underlying native lookup always includes gh auth token --hostname HOST --user USER for the exact configured identity. Both inherited credential families and debug tracing are cleared before lookup; credentials are passed only in the requested child's environment, never printed or persisted. API commands receive an explicit --hostname; other gh families receive GH_HOST and the host-qualified GH_REPO. Foreign-host selectors, global auth changes, token-output commands and replacement Authorization headers refuse. Literal body, title, jq and field values remain data; real gh owns its option grammar.

For shell consumers, source scripts/pinned-gh.sh and call caco_gh --project P -- <args>. Python consumers can launch pinned_gh.py with the same arguments. Neither entrypoint installs a gh executable, changes PATH or modifies stored auth state. Use the reviewed Cacophony helper path when working from another project's checkout, not a copied credential/config directory.

Existing HTTP release utilities receive credentials without shell token capture:

scripts/pinned-gh.sh --project example --expect-host github.com --exec -- python3 scripts/release-backfill-preflight.py github owner/repo v1.2.3 caco-linux-x86_64

--expect-host validates the configured destination; it does not override it. --expect-repo OWNER/REPO also requires the configured target repository, before any lookup or command. --expect-user USER fences a previously observed account, so a changed configuration invalidates a repair preview. --gh-bin PATH preserves explicit native/synthetic provider transports but applies the same exact host/account lookup to that executable; it is not a shell command or authentication bypass. Never use --exec to dump an environment or echo credentials. --exec --nix-source -- nix ... retains Github.com source-fetch authentication in that local Nix client's NIX_CONFIG, not argv, the parent shell, derivations, archives, images or remote builders. Local lookups are bounded and an outer timeout cleans up its owned keyring-lookup process group. The public installer bootstrap remains anonymous curl; authenticated checkout updates use this helper, and --public explicitly selects the credential-free path.

GitHub Actions exception

Actions job tokens are not node-global accounts. Only GITHUB_ACTIONS=true with an injected GH_TOKEN/GITHUB_TOKEN and valid HTTPS GITHUB_SERVER_URL plus GITHUB_REPOSITORY enables the shared helper's job-token path. That path still pins the destination host and never queries a stored account. Missing job credentials/metadata refuse rather than falling back to a runner's active login. Outside Actions, ambient token variables do not choose a project account.

The source guard's reviewed token-bound workflow entrypoints are ci-materialize-selected-ref.sh, ci-cache-contract.sh, ci-resolve-trusted-caco.sh, release-cache-manifest.sh, release-recover-failed-jobs.sh, and release-publish-if-assets-ready.sh under scripts/. Their workflows supply scoped credentials; they are not general operator entrypoints or a blanket exemption for ci-*. The shared release publisher can also receive an explicit pinned token through the backfill's --exec boundary; it refuses absent credentials and performs no account lookup.

Legacy multi-context callers

CallerTarget and credential context
release-tag.shExplicit cacophony route; existing checkout/build/install lifecycle unchanged
picasso-health-sync-action.shmono route, fenced to github.com/gim-home/harry; not the invoking Cacophony action's account
github-runner-runtime-health.shExplicit --project and exact repository; configured host/account also enter the repair fingerprint and receipt
pi-daemon-acceptance.pypi-daemon route, fenced to github.com/harryaskham/pi-daemon, retaining exact tag/asset/digest/package proof
android-ci-live-pr.pyActions' injected token or native --project cacophony route; provider failure still cannot emit proceed=true
Root justfile provider recipesExplicit cacophony route for secret setting, release download/upload, workflow status and queued-run cancellation
Android Culture prepare.pyNative paratenic route for the pinned source repository; Actions' explicit source-read credential for cross-repository archive reads
cs-gh-token.sh, just cluster-gh-loginRetired token-copy entrypoints refuse without looking up or exporting credentials

Release-tag changes are limited to GitHub routing and retain the parent's public pinning-helper snapshot. The existing checkout, build/artifact paths and local-update behavior are unchanged. Build-tree isolation is separate work in and requires a dry-run proof against the caco-release cron/admin-patch path, shared lock and result-file contract before adoption. This routing change does not authorize a live release or local binary replacement.

Runner local-only checks/preflight need no account lookup. Provider modes require a compatible caco; the Nix package retains the immutable pinning helper and required runtime dependencies. GITHUB_RUNNER_GH still selects the native/synthetic executable, but now both its lookup and provider command receive the configured identity. Existing node/unit/registration/label binding, broker scope, idle observations, preview confirmation and repair receipts remain mandatory. A changed provider host/account requires a new preview.

Pi-Daemon acceptance can bootstrap in Actions only with an injected token and GITHUB_SERVER_URL=https://github.com plus GITHUB_REPOSITORY=harryaskham/pi-daemon. An Actions job in another repository is not this bootstrap context; operator use requires the configured Pi-Daemon project instead of a runner's native login. The Android live-generation guard supports both Actions and operator diagnostics through the shared helper, so it needs no source-guard exception.

Android's companion/android/vendor/culture/prepare.py is a Cacophony-authored consumer materializer, not independently sealed producer SDK bytes. Its source-transport change leaves source.json, native artifact identity, the separate Rust SDK export/lock and provenance verifier unchanged; no producer source or pin update is needed. Only Actions may map CULTURE_SOURCE_READ_TOKEN to the scoped child credential, with valid job host/repository metadata; native invocation ignores that variable and all ambient token selectors. Archive commit/checksum verification, cache refusal, bounded redacted failures and partial-clone sync tripwires are unchanged.

Before caco/configuration exists, bootstrap anonymously with scripts/install.sh --public; there is no host-only/account-only token fallback. Supply the explicit project/repository/host/account through reviewed enrollment configuration, then use separately authorized first-party encrypted distribution or the configured scoped broker. See cs-pool bootstrap in the source repository for the input and ACL contract. Source delivery changes no installed account, credential, workflow, service or deployment.

First-party contract

caco-config::gh::ProjectGhRoute owns Caco's typed resolver and command-local launcher. Non-login shells prevent startup files from replacing the route. The routing function exists only in the first-party subprocess, never in agent startup or PATH.

Source guard and validation

python3 scripts/check-pinned-gh.py --base origin/main
python3 scripts/tests/pinned-gh-test.py
python3 scripts/tests/legacy-pinned-gh-test.py
python3 tests/pi-daemon-acceptance-fixtures.py
python3 scripts/tests/culture-preparation-test.py
bash tests/github-runner-runtime-health-fixtures.sh
python3 scripts/tests/check-pinned-gh-test.py
cargo test -p caco-config --lib gh:: -- --test-threads=1
python3 scripts/tests/managed-gh-lookup-test.py

The cheap source guard flags newly added direct gh subprocess calls and common shell command/substitution/alias forms, including justfile recipes. Only the two reviewed pinning implementations and explicit token-bound workflow paths are exempt. --all audits existing legacy callers as well; an unchanged historical caller is not permission to add another one. This is a source fence, not an authorization mechanism or complete shell compiler. Hermetic fixtures exercise exact host/user flags, wrong active/ambient accounts, native and Actions credential families, label inventory, deadlines, process cleanup, private installer behavior and transport failure before heavy builds; no operator auth files or provider are used.

Migration

Normal materialization/environment refresh removes only recognized daemon-generated bin/gh shims with a bounded descriptor reader. Unknown files are preserved and reported; legacy source-tool custody/receipts remain readable while shim publication refuses. Source merge does not update an already-running daemon, shell, script checkout or Pi session; compatible deployment and supported owner-controlled refresh remain separate stages. No credential, account or operator installation migration is performed by the source change. The former managed shim design is historical; and regressions are carried into typed routing tests rather than restoring a shim allowlist. This shared transport is used by TUI, web and Android consumers without a new surface-specific API.