Add fixture-only execution materialization and refresh (#1500)

This commit is contained in:
2026-09-10 20:20:44 -05:00
parent 27e4873acc
commit 3daee5ad89
10 changed files with 1163 additions and 5 deletions
@@ -1,7 +1,7 @@
# Increment 2 charter (#1500)
Status: draft, independent review requested before owner implementation approval.
Baseline b3fa221060abb820cd809f15c3a54ba84518b7a1. Owner requested faster continuation of charter drafting. Author/coordinator Darkwing; independent reviewer Filbert; Jason approves implementation. No source changes are authorized by this document yet.
Status: owner APPROVED the fixture-only implementation after prerequisite correction was independently verified and published. Implementation baseline: 27e4873accd4179acad61f1c4673fc65a4fa5f8b. Original draft and prerequisite context follow.
Baseline b3fa221060abb820cd809f15c3a54ba84518b7a1. Owner requested faster continuation of charter drafting. Author/coordinator Darkwing; independent reviewer Filbert; Jason approves implementation. The subsequent owner approval authorizes only the fixture-only stages and allowed paths below; no live activation is included.
## Outcome and boundary
@@ -0,0 +1,599 @@
# M20-I2-ISOLATION-ROCKO-1 — pi auth check/print isolation findings
Bounded read-only investigation (#1500). Brief:
`docs/plans/reviews/2026-09-10_m20-refresh-isolation-request.md`. Package
under investigation: `@earendil-works/[email protected]` (registry),
the same version currently pinned at the repo root (`package.json:8`;
verified directly for this correction — see section 0). Method: `npm
pack`/`npm view` against the public registry, local re-hash, static
reading of the extracted public package only. No CLI execution, no
credentials, no network calls, no installs.
## 0. Correction receipt (post-review, 2026-09-10)
Darkwing's review identified three evidence issues in the version of this
file at hash `89a9578c1a5982a2ef085e1ebff3990d66e8c237732397e2aba1c3b84d081ce9`.
Original claims are quoted verbatim below, followed by the correction.
Nothing in the original file has been deleted; corrected passages are
updated in place in the sections referenced.
**(1) Package pin.** Original (section 11): *"Did not compare 0.85.1's
auth-check isolation surface against the currently pinned 0.84.4
(`package.json:8`) line-by-line."* This was stale. Re-checked directly
against the current working tree: `package.json:8` now reads
`"@earendil-works/pi-coding-agent": "0.85.1"`, and `package-lock.json:12,533`
agree. `git log --oneline -- package.json` shows the pin was moved from
`0.84.4` to `0.85.1` by commit `557aba0f` ("Add packages/mosaic registry
schemas, validation, read-only CLI; pin pi 0.85.1 (#1499)"), which landed
after my original PI-REFRESH-ROCKO-1 read of `package.json:8` (when it
still said `0.84.4`) and before this review. Corrected in section 11
below: there is now no version delta between the pinned package and the
package under investigation — both are `0.85.1`.
**(2) Snapshot path.** Original (section 10): a timestamp placeholder,
`/tmp/m20-pi0851-isolation-<timestamp>/`. Corrected to the concrete,
still-present path used for every citation in this file:
`/tmp/m20-pi0851-isolation-20260910T222007Z/` (verified present with
`ls` at the time of this correction). Section 10 updated below.
**(3) `allowModelNetwork:false` scope.** Original claims (sections 5 and
9) stated flatly that "no live network call occurs" for any of the three
auth subcommands because `allowModelNetwork` is hard-coded `false`. This
was **incorrect as a general claim**`allowModelNetwork`/`allowNetwork`
only gates `ModelRuntime.refresh()`'s **model-catalog** refresh path
(`dist/core/model-runtime.js`). It does **not** reach OAuth **credential**
refresh, which is a separate, unconditional path traced fresh for this
correction in section 5a below and was not part of the original findings.
Sections 5, 8, and 9 are corrected below to bound the no-network claim to
model-catalog traffic only, and to state the OAuth-refresh network
exposure explicitly.
No CLI or network credential experiment was run to produce this
correction — the finding is from static reading of
`dist/bundle/chunks/chunk-IDDQWTHI.js` only, in the same snapshot already
cited.
## 1. Package identity/integrity (re-verified for this task)
Reused method from `docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md`
(hash `cfab6c006a5aeba1734390c01efa8231e201e6560bf422938385448b24054ac6`).
The original session-scratchpad extraction from that task was no longer
present (cleaned up at that task's close-out); Darkwing acknowledged this
before I re-fetched (reaction message, see brief context).
Two independent fetches into the shared snapshot (path below):
| Fetch | Command | Tarball shasum (npm pack manifest) |
|---|---|---|
| 1 | `npm pack @earendil-works/[email protected] --json` | `4cd00f653c3dabeb193b46f511044e7fbfe0f947` |
| 2 (addendum: lifecycle scripts disabled) | `npm pack @earendil-works/[email protected] --ignore-scripts --json` | `4cd00f653c3dabeb193b46f511044e7fbfe0f947` |
Identical shasum both times, and identical to the shasum recorded in the
prior PI-REFRESH-ROCKO-1 findings — the "0.85.1" identity used here is
byte-for-byte the same package. Also cross-checked against
`registry-dist.json` (`npm view @earendil-works/[email protected] dist
--json`), whose `shasum`/`integrity` match the local re-hash.
Note on lifecycle scripts: `npm pack <spec>` against a remote registry
package resolves and downloads the tarball directly — it does not run an
`npm install` and therefore never executes install lifecycle scripts,
independent of `--ignore-scripts`. The identical shasum with and without
the flag is expected and is not by itself proof the package has no
lifecycle scripts (`extracted/package/package.json` `scripts` block was not
separately audited here, out of scope); it is proof the fetch method used
for this snapshot did not execute any.
## 2. Isolation lever: `PI_CODING_AGENT_DIR`
`extracted/package/dist/config.js:406-427`:
```js
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
export function getAgentDir() {
const envDir = process.env[ENV_AGENT_DIR];
if (envDir) {
return expandTildePath(envDir);
}
return join(homedir(), CONFIG_DIR_NAME, "agent");
}
```
`APP_NAME` is `"pi"` (`dist/config.js:401`, `piConfigName || "pi"`), so
`ENV_AGENT_DIR` resolves to the literal string `PI_CODING_AGENT_DIR`.
`CONFIG_DIR_NAME` is `.pi` by default (`dist/config.js:403`). With the env
var unset, the fallback is `homedir()` (Node's `os.homedir()`, itself
`HOME`-driven on POSIX) joined with `.pi/agent` — so `HOME` also moves the
default location, but `PI_CODING_AGENT_DIR` is the explicit, single-purpose
override and does not disturb any other `HOME`-dependent behavior.
Every agent-state path is derived from `getAgentDir()` and therefore moves
together under one env var (`dist/config.js:433-461`):
```js
export function getModelsPath() { return join(getAgentDir(), "models.json"); }
export function getAuthPath() { return join(getAgentDir(), "auth.json"); }
export function getSettingsPath() { return join(getAgentDir(), "settings.json"); }
export function getToolsDir() { return join(getAgentDir(), "tools"); }
export function getBinDir() { return join(getAgentDir(), "bin"); }
export function getPromptsDir() { return join(getAgentDir(), "prompts"); }
export function getSessionsDir() { return join(getAgentDir(), "sessions"); }
export function getDebugLogPath() { return join(getAgentDir(), `${APP_NAME}-debug.log`); }
```
`getPackageDir()` (`dist/config.js:311-313`) is a **separate**,
unrelated env var (`PI_PACKAGE_DIR`) that locates the *installed package
itself* (themes, docs, README, CHANGELOG bundled with the npm package) —
it has no bearing on agent state and is not part of the isolation
boundary being evaluated.
**No dedicated CLI flag exists for auth path override.** `auth-command.js`
(`dist/cli/auth-command.js`) defines only `--provider`, `--model`,
`--json`, `--credentials`, `--no-refresh` (check) and `--min-expiry`
(print-bearer-token) — see `AUTH_COMMAND_USAGE` at
`dist/cli/auth-command.js:4-8` and the flag parser at
`dist/cli/auth-command.js:27-75`. There is no `--auth-path`,
`--config-dir`, or similar. `PI_CODING_AGENT_DIR` (optionally combined
with a scratch `HOME`) is the only supported isolation mechanism.
## 3. CLI dispatch order — auth commands run before cwd/settings/extension/session/trust setup
Confirmed directly from the bundled entry point, not inferred from naming.
`dist/bundle/cli.js` is the npm `bin.pi` shim; it calls
`main(process.argv.slice(2))` from the single bundled chunk
`dist/bundle/chunks/chunk-JVUZSMYM.js`. That chunk is a single minified
line (4,006,956 bytes); functions were located by string search
(`indexOf`) rather than line numbers — anchors below are function names,
which are preserved (not mangled) in this build.
`async function main(args, options)` (found at byte offset 3990752),
head of body, statement order preserved verbatim except formatting:
```js
async function main(args,options){
resetTimings();
let extensionFactories=[...builtInExtensions,...options?.extensionFactories??[]],
offlineMode=args.includes("--offline")||isTruthyEnvFlag2(process.env.PI_OFFLINE);
if(offlineMode&&(process.env.PI_OFFLINE="1",process.env.PI_SKIP_VERSION_CHECK="1"),
await runAuthCommand(args))
return;
process.platform==="win32"&&cleanupWindowsSelfUpdateQuarantine(getPackageDir()),
cleanupManagedInstall();
let cwd=process.cwd(),
agentDir=getAgentDir(),
bootstrapSettingsManager=SettingsManager.create(cwd,agentDir,{projectTrusted:!1});
if(applyHttpProxySettings(bootstrapSettingsManager.getGlobalSettings().httpProxy),
configureHttpDispatcher(),
await handlePackageCommand(args,{extensionFactories})){ ... }
if(await handleConfigCommand(args,{extensionFactories}))return;
let parsed=parseArgs(args);
...
```
This settles the brief's explicit question with evidence, not an inference
from command naming: `await runAuthCommand(args)` is the **first**
awaited call in `main()`, and a truthy return **exits `main()` before**:
- `process.cwd()` is read (`cwd` is assigned only after the auth branch),
- `SettingsManager.create(cwd, agentDir, {projectTrusted:false})` runs
(project trust / project settings discovery),
- `handlePackageCommand`/`handleConfigCommand` run,
- `parseArgs(args)` runs (the general CLI arg parser, session/model/tool
flags),
- any session manager, migrations, or interactive-mode setup is reached.
The only thing evaluated *before* `runAuthCommand` is the
`extensionFactories` array assembly: `builtInExtensions` is a static
in-memory literal —
`builtInExtensions=[{name:"llama.cpp",factory:llamaExtension,hidden:!0}]`
— with no disk I/O at construction, and `options?.extensionFactories` are
factories the *host process* passes in (not loaded from a project
directory or config file). So no project-scoped extension or config
discovery occurs ahead of auth dispatch either.
`runAuthCommand(args)` itself (byte offset 3981368) calls `getAgentDir()`
indirectly (via `AuthStorage.create()` / `ReadOnlyAuthStorage` /
`ModelRuntime.create()` default paths — section 4) but never reads `cwd`,
never touches `SettingsManager`, and never resolves project-level
extensions.
## 4. Auth storage isolation specifics
`dist/core/auth-storage.js`:
- `AuthStorage.create(authPath = join(getAgentDir(), "auth.json"))`
(line 281) — used by `check` unless `--no-refresh` is given.
- `ReadOnlyAuthStorage` constructor:
`constructor(authPath = join(getAgentDir(), "auth.json"))` (line 161) —
used by `check --no-refresh`.
- `FileAuthStorageBackend` (line 17) is the backing store for
`AuthStorage`; its lock file is acquired directly on the auth path via
`proper-lockfile`: `lockfile.lockSync(path, {realpath:false})` (line 39)
and `lockfile.lock(this.authPath, ...)` (line 85). `proper-lockfile`
places its lock next to the target file, so the lock stays inside
`getAgentDir()` — no isolation-breaking temp path outside the agent dir.
- Neither constructor, nor any auth-path CLI flag, exists to point
`check`/`print-*` at a path outside `getAgentDir()` independent of the
env var — confirming `PI_CODING_AGENT_DIR` (optionally with a scratch
`HOME`) is the complete and only isolation lever for credentials.
`print-api-key`/`print-bearer-token` (the `command.kind!=="check"` branch
inside `runAuthCommand`, same byte-offset function) call
`ModelRuntime.create({allowModelNetwork:false, signal})` with no explicit
`credentials`/`authPath` option, so `ModelRuntime.create`
(`dist/core/model-runtime.js:74`) falls back to its own default:
`DefaultAuthStorage.create(options.authPath)` with `options.authPath`
undefined — which resolves to the same `getAgentDir()`-derived
`auth.json` path. Credential *file location* is identical across all
three subcommands; the models-catalog behavior differs (section 5), and
so does credential-refresh network exposure across `check`/`check
--no-refresh`/`print-*` (section 5a).
## 5. `check` vs `print-*`: models.json / models-store.json asymmetry, and model-catalog network scope (corrected)
`dist/cli/auth-check.js:42-49`:
```js
export async function createAuthCheckModelRuntime(credentials) {
return ModelRuntime.create({
credentials,
modelsStore: new InMemoryCodingAgentModelsStore(),
allowModelNetwork: false,
refreshOnCreate: false,
});
}
```
`check` passes an explicit in-memory models store and disables
`refreshOnCreate` — it never reads `models.json`/`models-store.json` from
disk and never runs `runtime.refresh()` at construction.
`print-api-key`/`print-bearer-token`, by contrast, call
`ModelRuntime.create({allowModelNetwork:false, signal})` with no
`modelsStore`/`modelsPath`/`refreshOnCreate` override. `ModelRuntime.create`
defaults (`dist/core/model-runtime.js:74-108`):
```js
static async create(options = {}) {
const credentials = new RuntimeCredentials(options.credentials ?? DefaultAuthStorage.create(options.authPath));
const modelsPath = options.modelsPath === null ? undefined : (options.modelsPath ?? join(getAgentDir(), "models.json"));
const config = await ModelConfig.load(modelsPath);
const modelsStore = options.modelsStore ??
(modelsPath
? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), "models-store.json"))
: new InMemoryCodingAgentModelsStore());
...
const refreshFromNetwork = runtime.modelNetworkEnabled && options.allowModelNetwork === true;
...
if (options.refreshOnCreate !== false) {
await runtime.refresh({ allowNetwork: refreshFromNetwork, signal });
}
...
}
```
So `print-*` **does** read `models.json` and `models-store.json` from
`getAgentDir()` (both env-overridable together with everything else), and
**does** call `runtime.refresh()` at construction (since
`refreshOnCreate` is not passed as `false` here) — this is a real
behavioral difference from `check`. `allowModelNetwork` is fixed to
`false` for both `check`'s in-memory runtime and `print-*`'s runtime, so
`refreshFromNetwork = runtime.modelNetworkEnabled && options.allowModelNetwork === true`
evaluates to `false` regardless of `modelNetworkEnabled`. `refresh()`
(`dist/core/model-runtime.js:504-517`) forwards this as
`allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled` into the
underlying `this.models.refresh(refreshOptions)` (the `pi-ai` models
store) — **this specific call path** performs no network-based
**model-catalog** refresh for any of the three auth subcommands. The
only disk reads triggered by it are local: `ModelConfig.load(modelsPath)`
(`models.json`) and whatever `FileModelsStore` does with
`models-store.json`, both under `getAgentDir()`.
**Practical isolation consequence (catalog only):** an isolated
`PI_CODING_AGENT_DIR` needs, at minimum, a valid (possibly
empty-provider) `models.json` to exist or be absent-tolerant for
`print-*` to construct cleanly, since it does load that file; `check`
has no such dependency.
**This "no network" finding is scoped to model-catalog refresh only and
does not extend to OAuth credential refresh — see section 5a, added on
review correction.** The original text here claimed "no live network
call occurs under an isolated, offline-style setup" without that scope
qualifier; that unqualified claim was incorrect and is retracted in favor
of section 5a.
### 5a. OAuth credential refresh is a *separate*, unconditional network path (correction — traced 2026-09-10)
`ModelRuntime.getAuth()`/`checkAuth()` (`dist/core/model-runtime.js:280,339`)
delegate to `this.models.getAuth`/`checkAuth` — methods on the
`pi-ai`-level models store, implemented in
`dist/bundle/chunks/chunk-IDDQWTHI.js` (same snapshot,
`extracted/package/dist/bundle/chunks/chunk-IDDQWTHI.js`), **not** in
`dist/core/model-runtime.js`. Neither `allowModelNetwork` nor
`allowNetwork` is threaded into this call at all — `getAuth`
(offset 15594) calls `resolveProviderAuth(provider, this.credentials,
this.authContext, {...overrides, signal})` with no network flag among
`overrides`.
`resolveProviderAuth``resolveStoredOAuth` (offset 4923 in that chunk)
is where an OAuth credential's freshness is enforced, **unconditionally,
with no network gate anywhere in the function**:
```js
var DEFAULT_OAUTH_MINIMUM_VALIDITY_MS=300*1e3, DEFAULT_OAUTH_REFRESH_TIMEOUT_MS=15e3;
async function resolveStoredOAuth(credentials,providerId,oauth,stored,signal,minOAuthValidityMs){
let minimumValidityMs=Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS,minOAuthValidityMs??0),
expiresSoon=credential2=>Date.now()+minimumValidityMs>=credential2.expires,
credential=stored;
if(expiresSoon(credential)){
let post;
try{
post=await credentials.modify(providerId,async current=>{
if(current?.type==="oauth"&&expiresSoon(current))
try{
let refreshSignal=AbortSignal.any([signal,AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)]);
return await oauth.refresh(current,refreshSignal)
}catch(error){throw new ModelsError("oauth",`OAuth refresh failed for ${providerId}`,{cause:error})}
},{signal})
}catch(error){ ... }
...
}
...
}
```
If a stored OAuth credential's `expires` timestamp is within
`DEFAULT_OAUTH_MINIMUM_VALIDITY_MS` (300,000 ms / 5 minutes) of `Date.now()`
— including already expired — this path calls `oauth.refresh(current,
refreshSignal)` **regardless of `allowModelNetwork`/`allowNetwork`, which
this function never receives**. `oauth.refresh` is the per-provider
implementation; for Anthropic it is `refreshAnthropicToken` posting to
`TOKEN_URL="https://platform.claude.com/v1/oauth/token"`
(`dist/bundle/chunks/anthropic.js`, confirmed present in this snapshot
with that exact literal), and equivalent live token endpoints exist for
other bundled providers (`openai-codex.js``auth.openai.com/oauth/token`,
`openrouter.js``openrouter.ai/api/v1/auth/keys`, `xai.js`
`auth.x.ai/oauth2/token`). This is a real outbound HTTPS call, not a
local operation.
**Which subcommands are exposed, and the one mitigation that exists:**
- `check` (default, no `--no-refresh`): uses `AuthStorage.create()`
(mutable) → `credentials.modify()` succeeds → `oauth.refresh()` runs if
a stored OAuth credential is near/at expiry.
- `check --no-refresh`: uses `ReadOnlyAuthStorage`, whose `modify()`
**throws immediately** (`dist/core/auth-storage.js:227`,
`"Read-only credential storage cannot modify auth.json"`) *before*
`oauth.refresh()` can be reached. The thrown error propagates out of
`resolveStoredOAuth`/`getAuth` and is caught by `checkProviderAuth`'s
try/catch (`dist/cli/auth-check.js:32-34`), which reports
`{status:"invalid", reason:"invalid_state"}` instead. **This is the
only one of the three subcommands with a supported way to guarantee no
OAuth-refresh network attempt.**
- `print-api-key` / `print-bearer-token`: always construct
`ModelRuntime.create({allowModelNetwork:false, signal})` with no
`credentials` override, which resolves to `DefaultAuthStorage.create()`
— the same **mutable** `AuthStorage` as default `check`. There is
**no `--no-refresh` equivalent flag** for either print command
(`AUTH_COMMAND_USAGE`/flag parser, `dist/cli/auth-command.js:4-8,27-75`
restrict `--no-refresh` to `kind==="check"`). So both print commands
will attempt a live OAuth-refresh network call whenever the isolated
`auth.json` holds a stored OAuth credential within 5 minutes of expiry
(or already expired), with no way to suppress it via flags.
**Bounded conclusion:** for an *empty* isolated `auth.json` (or one
containing only `api_key`-type credentials, which never enter
`resolveStoredOAuth` at all — `readCredential` in
`resolveProviderAuthWithSignal` branches on `stored.type==="oauth"`
before this path is reached), none of the three subcommands makes any
network call, matching the original (uncorrected) intent. The risk is
specific and load-bearing: **never place a real, or a synthetic
near-expiry, OAuth-type credential in an isolated `PI_CODING_AGENT_DIR`
used for `print-api-key`/`print-bearer-token` testing, or in `check`
without `--no-refresh`** — doing so can trigger a genuine outbound
request to the provider's live OAuth token endpoint, independent of
`allowModelNetwork`. No such credential or request was created, read, or
attempted in this investigation; this is a static-source-derived bound,
not an observed network call.
## 6. `models.json` custom-provider schema (Darkwing addendum)
`docs/models.md` (top-level `docs/` in the extracted 0.85.1 tarball,
included in the shared snapshot) documents the format; the authoritative
schema is `dist/core/model-config.js:139-187` (TypeBox), reproduced in
relevant part:
```js
const ModelDefinitionSchema = Type.Object({
id: Type.String({ minLength: 1 }),
name: Type.Optional(...), api: Type.Optional(...), baseUrl: Type.Optional(...),
reasoning: Type.Optional(...), thinkingLevelMap: Type.Optional(...),
input: Type.Optional(...), cost: Type.Optional(...),
contextWindow: Type.Optional(...), maxTokens: Type.Optional(...),
samplingParams: Type.Optional(...), headers: Type.Optional(...),
compat: Type.Optional(...),
});
const ProviderConfigSchema = Type.Object({
name: Type.Optional(...), baseUrl: Type.Optional(Type.String({minLength:1})),
apiKey: Type.Optional(Type.String({minLength:1})), api: Type.Optional(Type.String({minLength:1})),
oauth: Type.Optional(Type.Literal("radius")), headers: Type.Optional(...),
compat: Type.Optional(...), authHeader: Type.Optional(...),
models: Type.Optional(Type.Array(ModelDefinitionSchema)),
modelOverrides: Type.Optional(...),
});
const ModelsConfigSchema = Type.Object({
providers: Type.Record(Type.String(), ProviderConfigSchema),
});
```
This confirms Darkwing's proposed minimal shape is schema-valid against
0.85.1's actual `models.json` validator:
```json
{
"providers": {
"<id>": {
"api": "...",
"baseUrl": "...",
"models": [{ "id": "..." }],
"apiKey": "... (optional)"
}
}
}
```
`models.id` is the only field in the entire schema with no
`Type.Optional` wrapper anywhere in the object graph — every other field
at every level is optional. `docs/models.md`'s own "Minimal Example" uses
exactly this shape (an Ollama provider with `api`, `baseUrl`, `apiKey`,
and a `models` array of bare `{id}` objects), matching the schema
reading. `apiKey` is confirmed optional both in the schema and in
`docs/models.md`'s notes on keyless local servers.
## 7. Non-secret output and exit codes
Source: `runAuthCommand` (byte offset 3981368) and
`resolveCredentialForPrint` (found by name in the same chunk), plus
`dist/cli/auth-command.js` / `dist/cli/auth-check.js`.
**Parse-level errors (any subcommand, before dispatch):**
- Unknown `auth` subcommand or unparseable args → `console.error` in red
(`Error: <message>`), `process.exitCode = 1`.
- Unknown flag → `console.error` (`Unknown option --<flag> for "<command
name>".`) + a dim usage hint, `process.exitCode = 1`.
- `--help`/`-h`/bare `auth` → `printAuthCommandHelp()` prints the usage
block to stdout via `console.log`, no exit code forced (defaults to 0).
**`print-api-key` / `print-bearer-token`:**
- Success: the resolved credential/token string is written to stdout via
`process.stdout.write(`${credential}\n`)`. No JSON mode, no structured
status — bare secret value only. `process.exitCode` is left at its
default (0) on success.
- Failure (`AuthCommandError` or unexpected throw, e.g. unknown provider,
no credential configured, model not found): `console.error` in red
(`Error: <message>`), `process.exitCode = 1` (kind is not `"check"`, so
the kind-dependent exit branch sets `1`).
**`check`:**
- `result.status` is one of `"ready" | "not_ready" | "invalid"`
(`checkProviderAuth`, `dist/cli/auth-check.js:5-35`), with a `reason`
code among `invalid_state | provider_not_found |
credentials_not_configured | credential_not_available` when not ready.
- Default (non-`--json`) output: `credential ?? result.status` written to
stdout (i.e. the bare status word unless `--credentials` requested and
status is `ready`, in which case the raw credential is printed instead —
operators should not pass `--credentials` in a context where stdout is
logged/shared if avoiding secret material matters).
- `--json` output: `JSON.stringify({...result, ...(credential ? {credentials: credential} : {})})`
— i.e. `{"status":"ready","provider":"...","authType":"..."}` (plus a
`credentials` field only when `--credentials` was also passed).
- Exit codes: `result.status==="ready" ? 0 : result.status==="not_ready" ? 1 : 2`.
- On a thrown error during `check` (e.g. missing `--provider`/`--model`):
`console.error` (`Error: <message>`), `process.exitCode = 2` (kind is
`"check"`).
No path observed prints raw OAuth `refresh` tokens or API keys unless the
operator explicitly passes `--credentials` (`check`) or invokes
`print-api-key`/`print-bearer-token` directly, which is their documented
purpose. This finding itself contains no credential material — all
values above are format descriptions only.
## 8. Direct answers to the brief
- **Exact supported environment/flags for isolation:** set
`PI_CODING_AGENT_DIR` to a dedicated empty directory (this alone
isolates `auth.json`, `models.json`, `models-store.json`,
`settings.json`, sessions, tools, bin, prompts, and the debug log).
Optionally also set a scratch `HOME` as defense-in-depth, since it is
the fallback base if `PI_CODING_AGENT_DIR` were ever unset by mistake,
but it is not required given `PI_CODING_AGENT_DIR` takes precedence
unconditionally. No CLI flag exists for this; it is env-only.
- **Working directory relevance:** none, for auth commands specifically —
proven in section 3 (`cwd` is not read until after `runAuthCommand`
returns).
- **Does auth-check load extensions/project packages/settings before
handling the command?** No — proven in section 3 by direct reading of
`main()`'s statement order, not inferred from naming.
- **Is `PI_CODING_AGENT_DIR` (or another override) supported?** Yes,
confirmed at `dist/config.js:406-427`; it is the sole override and
controls the entire agent directory as one unit.
- **Non-secret output and exit codes:** enumerated in section 7.
- **Network scope (corrected):** `allowModelNetwork:false` blocks only
the model-catalog refresh path (section 5); it does not gate OAuth
credential refresh, which is a separate, unconditional path that can
make a live call to the provider's OAuth token endpoint if a stored
OAuth credential is near/at expiry (section 5a).
## 9. Blocker determination (corrected)
**No blocker identified for the isolation mechanism itself**, with one
load-bearing constraint on credential content that was missing from the
original version of this section. `PI_CODING_AGENT_DIR` fully isolates
the auth/config directory:
- Credential resolution (`auth.json`) is isolated identically across all
three subcommands (section 4).
- No project/cwd/extension/settings discovery occurs before or during
auth-command handling (section 3).
- Model-catalog refresh performs no network call under
`allowModelNetwork:false`, which is hard-coded for all three
subcommands, not operator-controlled (section 5).
- The one residual gap for `print-*` is that it also touches
`models.json`/`models-store.json` under the same isolated directory
(section 5) — this is a same-directory read, not an isolation leak, but
callers should ensure the isolated `PI_CODING_AGENT_DIR` either omits
`models.json` (tolerated — `ModelConfig.load` handles a missing file) or
contains only the intended synthetic provider (section 6 shape) if
`print-*` isolation tests need deterministic provider resolution.
- **Corrected/added:** credential *content* placed in the isolated
`auth.json` is load-bearing for network isolation, independent of
`PI_CODING_AGENT_DIR`. An empty `auth.json`, or one containing only
`api_key`-type entries, triggers no network call from any of the three
subcommands. A stored `oauth`-type credential within 5 minutes of its
`expires` timestamp (or already expired) will trigger a live refresh
request to the provider's real OAuth token endpoint for default `check`
and for both `print-*` commands (no flag suppresses this for `print-*`;
`check --no-refresh` is the one subcommand/flag combination that
provably cannot reach the network for this reason — section 5a). This
is a constraint on test fixture design, not a defect in
`PI_CODING_AGENT_DIR` isolation, but it must be honored for any
isolation test that populates `auth.json` with OAuth-shaped data.
## 10. Shared snapshot (corrected — concrete path)
`/tmp/m20-pi0851-isolation-20260910T222007Z/` (verified present on this
host at the time of this correction; coordinator/reviewer-accessible for
inspection). Contents:
tarball, `pack.json`/`pack-ignore-scripts.json` (npm pack manifests),
`registry-dist.json` (npm view dist metadata), `tarball.sha256`, and
`extracted/package/` (full public tarball contents: `dist/`, `docs/`
including `docs/models.md`, `CHANGELOG.md`, `README.md`, `package.json`).
See `SNAPSHOT-INDEX.md` inside the snapshot for a file-by-file map to the
citations above.
## 11. What this investigation did not do
- Did not run `pi` (any subcommand), install the package, or use real or
fake credentials.
- Did not audit `extracted/package/package.json`'s `scripts` block for
the presence/absence of lifecycle scripts (noted as out of scope in
section 1's caveat).
- Did not trace `FileModelsStore`/`ModelConfig.load`'s exact disk-miss
behavior line-by-line beyond confirming it is called and is local
(no network) under `allowModelNetwork:false` for the model-catalog path
specifically (section 5).
- Did not verify behavior on Windows (`getAgentDir`'s `expandTildePath`/
path-join behavior assumed POSIX-equivalent per this host).
- **Corrected:** the package pin comparison this bullet originally
described is moot — `package.json:8` now pins `0.85.1`, the same
version investigated throughout this file (see section 0(1)). The pin
was `0.84.4` when read during the prior PI-REFRESH-ROCKO-1 task and was
moved to `0.85.1` by commit `557aba0f` (#1499) before this task ran.
There is no version delta left to compare for the auth/isolation
surface; PI-REFRESH-ROCKO-1's changelog analysis (0.84.4→0.85.1, no
auth-related changes) remains the relevant prior evidence for anything
that predates the pin bump.
- Did not run any command, real or synthetic, that would populate
`auth.json` with an OAuth-type credential to observe the refresh path
in section 5a directly — that section is derived entirely from static
reading of `chunk-IDDQWTHI.js`, per the brief's "no CLI/network
credential experiment authorized" constraint from the review.