Repository navigation
PI_OFFLINE silently disables all provider model discovery — undocumented behavior contradicting its documented scope #8684
Description
Activity
github-actions commented
on Aug 26, 2026 on Aug 26, 2026 – with GitHub ActionsContributorMore actionsThis issue was auto-closed. All issues from new contributors are auto-closed by default.
Maintainers review auto-closed issues daily and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply.
If a maintainer replies
lgtmion one of your issues, your future issues will stay open. If a maintainer replieslgtm, your future issues and PRs will stay open. The command must be at the start of the reply (optionally after one or more@usernamementions) or at the end.See CONTRIBUTING.md.
- addeduntriagedThis issue has been auto closed and has not been triagedThis issue has been auto closed and has not been triaged
on Aug 26, 2026 @mitsuhiko What is your thought on this? What is the real and expected meaning of
PI_OFFLINE?- removeduntriagedThis issue has been auto closed and has not been triagedThis issue has been auto closed and has not been triaged
on Sep 1, 2026 Takeaway: confirmed on 0.84.4. Any value of PI_OFFLINE, including 0 and empty, stops catalog discovery in rpc mode, static providers are unaffected, and the fix is to parse the variable in model-runtime.ts the same way main.ts already does.
I run pi in rpc mode in production with PI_OFFLINE=1, so I tested against a fake local server that logs every request, with one static models.json provider and one extension provider using createProvider with fetchModels (the same refreshModels path litellm uses). With PI_OFFLINE unset, an rpc session sends exactly one GET /v1/models and get_available_models lists the discovered models. With PI_OFFLINE=1, PI_OFFLINE=0, PI_OFFLINE= or --offline, the server never sees a request and only static models come back, with nothing in the output saying why.
The === undefined check is the cause: patching that one test in the installed bundle to a 1/true/yes check made 0 and empty discover again while 1 stayed offline, and restoring it made them go dark again. Static models.json providers and the built-in catalog worked in every case, and a previously discovered model cached in models-store.json kept working offline. One difference from the report: pi --list-models never triggers discovery for me even with the variable unset, since that runtime is built without allowModelNetwork; the contrast only shows with a warm cache.
So the flag really does disable discovery for any value. main.ts already parses it with isTruthyEnvFlag for offlineMode; using the same parse in model-runtime.ts fixes 0 and empty, and a line in docs/settings.md saying PI_OFFLINE also stops catalog discovery covers the rest.
I can take this. Root cause confirmed as described:
model-runtime.tsgatesmodelNetworkEnabledwithprocess.env.PI_OFFLINE === undefined(any value — evenPI_OFFLINE=0or empty — trips it), andrefresh()defaultsallowNetworkto it, so all network discovery silently dies for the session. This also contradicts the--offline/--helpdocs, which say1/true/yes.Planned fix, scoped to the uncontroversial parts first:
- Replace the
=== undefinedcheck with a truthy1/true/yescheck matching the documented CLI semantics. - Surface the skip: when a refresh bails because of offline mode, include that in the refresh result / surface a warning instead of failing indistinguishably from "provider unreachable".
- Update the three doc sites (settings.md, environment-variables.md,
--help) to state that automatic model-catalog network discovery is also disabled.
One open question for you: the deeper fix is narrowing the default to match the
llamaextension's contract (offline suppresses startup/automatic discovery only, user-initiated refreshes like/modelstill hit the network). That requires marking whichrefresh()call sites are user-initiated (model-registry,model-catalog-refresh, etc. currently pass bare{ signal }), so it touches several call paths. Happy to include that if you want it in the same change — otherwise I'd keep this PR to 1-3 and leave the narrowing as a follow-up.Requesting
lgtmto open a PR.This comment is AI-generated by
/wr.- Replace the
I went ahead and put a patch together since I'd already reproduced this: main...chiptoe-svg:pi:fix/pi-offline-parse
It's one shared
isOfflineModeEnabled()with the 1/true/yes parse (main.ts, the package manager and the tools manager each already had their own copy of it), and every PI_OFFLINE read goes through it. That covers the=== undefinedcheck in model-runtime.ts plus four other spots that were testing for presence or plain truthiness - radius auth, the version check, and the three startup checks in interactive mode. Docs and --help now mention catalog discovery.I kept the narrowing question (user-initiated /model refreshes under offline) out of it - that's your call.
Tests cover the parser and what allowNetwork a dynamic provider gets from the default refresh() under unset/0/empty/1; the 0 and empty cases fail without the fix. biome, tsgo and the runtime suites pass.
@holny happy to hand this over if you'd rather run with it.
Would appreciate an lgtm so I can open the PR.
Confirmed on macOS. The root cause is sharper than "any value disables":
PI_OFFLINEis parsed three different ways across the codebase, andmodel-runtime.tsuses the strictest one.core/model-runtime.ts:196passesprocess.env.PI_OFFLINE === undefinedasmodelNetworkEnabled. SoPI_OFFLINE=0or=""(a user explicitly trying to turn offline off) still disables catalog discovery — the opposite of intent.utils/version-check.ts:55uses a truthy check, soPI_OFFLINE=0/=falsealso disables version checks.main.ts:565usesisTruthyEnvFlag(...), matching the docs incli/args.ts:433("set to 1/true/yes").
isTruthyEnvFlag(main.ts:106) returns true only for1/true/yes.model-runtime.ts:196is the outlier.Fix: replace
process.env.PI_OFFLINE === undefinedwith!isTruthyEnvFlag(process.env.PI_OFFLINE)in model-runtime.ts:196, and align version-check.ts:55 the same way. This bites most in rpc mode, which skips main.ts:565's--offline→PI_OFFLINE="1"normalization, so a strayPI_OFFLINE=0in the env isn't rewritten.Notable:
model-runtime.tshas no direct test for thePI_OFFLINE→modelNetworkEnabledpath (those tests live inpackage-manager.test.ts), which is why the regression went uncaught.
Drafted with AI assistance; reviewed and shaped by me before posting.
Nudge — my fix for this is committed on a branch rebased against latest main (tests green). chiptoe-svg also has a compare-link patch upthread, so a lgtm for either approach would unblock this. (Drafted with AI assistance per CONTRIBUTING.md, reviewed and posted by me.)
Another nudge from me — fix is ready and tested, happy to open a PR whenever a maintainer has a minute. Also open to adjusting the approach if you'd prefer something else.
I think you should just open the PR, can't you open it?
@mxr576 not until they say LGTM
1 remaining item
@mitsuhiko Hi, I see major improvements are merged day by day to PI! Kudos! 🍻
I wonder, is this small issue and the proposed fix(eS) on the team's radar? Could these guy get a LGTM so they can open a PR for review?
- added 2 commits that reference this issue
on Sep 22, 2026 - added a commit that references this issue
on Sep 22, 2026 - added a commit that references this issue
on Sep 23, 2026 - added 2 commits that reference this issue
on Sep 23, 2026 - added a commit that references this issue
on Sep 26, 2026 - added a commit that references this issue
on Oct 7, 2026 - added a commit that references this issue
on Oct 8, 2026 - added a commit that references this issue
on Oct 9, 2026
What happened?
PI_OFFLINEis documented as disabling only startup housekeeping network operations (update checks, package update checks, install/update telemetry). In practice, it also disables all provider model-catalog network discovery for the entire session — this is undocumented and easy to hit by accident.Verified this is core behavior, not an extension issue, by reading the source directly (not just via
pi -ne, since the affected code path doesn't touch any extension at all):packages/coding-agent/src/core/model-runtime.ts, lines 190–196:
ts const runtime = new ModelRuntime( credentials, config, modelsPath, modelsStore, providers, process.env.PI_OFFLINE === undefined, // becomes `modelNetworkEnabled` ); That value is stored as
modelNetworkEnabledand used as the defaultallowNetworkfor every model refresh, line 701:
ts const refreshOptions = { ...options, allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled, }; So merely having
PI_OFFLINEset in the environment (to any value — the check is=== undefined, not a truthiness/1check, so evenPI_OFFLINE=0orPI_OFFLINE=triggers it) disables network-based model discovery everywhere that doesn't explicitly overrideallowNetwork: true. Providers relying on the default refresh path never discover models for the whole session, with no error and no indication this is what happened.This contradicts the documented scope of
PI_OFFLINE:packages/coding-agent/docs/settings.md, line 84: "Use--offlineorPI_OFFLINE=1to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry." (model discovery is not "described here")packages/coding-agent/docs/environment-variables.md, line 84: "Disable startup network operations, including update checks, package updates, and install/update telemetry" (same omission)packages/coding-agent/src/cli/args.ts, line 433 (--helptext): "Disable startup network operations when set to 1/true/yes" (same omission)It's also inconsistent with pi's own built-in
llamaextension, which deliberately keeps user-initiated model discovery alive underPI_OFFLINE:https://github.com/earendil-works/pi/blob/v0.84.3/packages/coding-agent/src/extensions/llama/index.ts#L54-L58
That comment establishes the intended contract:
PI_OFFLINEshould suppress automatic/startup network access, while a user-initiated action against an explicitly-configured provider is a sanctioned exception. ThemodelNetworkEnableddefault inmodel-runtime.tsapplies the offline gate much more broadly than that — any provider/extension that doesn't specially overrideallowNetwork: true(asllamadoes) loses discovery entirely, including in response to explicit user actions like opening/model.This matters in practice for anyone setting
PI_OFFLINE=1purely for its documented purpose — e.g. in an immutable/sandboxed environment (such as a DDEV add-on) to suppress update-check/telemetry phone-home — who then finds an explicitly configured, reachable model provider shows zero models with no explanation.Steps to reproduce
. Configure any provider that relies on network model discovery through the default
ModelRuntime.refresh()path (not one that special-casesallowNetwork: truelikellamadoes) — e.g. thelitellmprovider viaLITELLM_BASE_URL/LITELLM_API_KEYfrom thepi-provider-litellmextension, pointed at a real, reachable endpoint.2. Set
PI_OFFLINE=1in the environment.3. Run
pi --list-models(or start pi interactively and open/model).Result: no models are discovered, with no error message referencing
PI_OFFLINEor offline mode anywhere.PI_OFFLINEand repeat step 3 with identical configuration: models are discovered normally.Expected behavior
One of:
PI_OFFLINE's documentation (settings.md, environment-variables.md, --help text) explicitly states that it also disables automatic model-catalog network discovery, so this isn't a surprise; and/orPI_OFFLINEinto its own flag, since users setPI_OFFLINEfor its documented, narrower purpose (no update/telemetry phone-home) and don't expect it to also silently break model discovery for an explicitly configured provider; and/ormodelNetworkEnableddefault is narrowed to match thellamaextension's contract: suppress only automatic/startup discovery underPI_OFFLINE, while still allowing user-initiated discovery (e.g. opening/model) to proceed, consistent with the exceptionllamaalready carves out for itself.At minimum, when discovery is skipped due to
PI_OFFLINE, this should be surfaced to the user (or to extensions, via the refresh result) rather than failing indistinguishably from "not configured" or "provider unreachable".Version
0.84.3