fix: support environment-variable-only configuration (doctor misdiagnosis + OAuth config-file side effect) - #1569
Merged
Aaronontheweb merged 4 commits intoJul 3, 2026
Conversation
The daemon binds NETCLAW_ env vars over an OPTIONAL json file, so an
instance with no netclaw.json can be fully configured and healthy — but
every doctor check that consults DoctorJsonConfigReader.TryReadConfig
warned 'daemon not configured - please run netclaw init' on file absence.
- TryReadConfig: when the file is missing but NETCLAW_ config-binding env
vars (double-underscore section keys) are present, short-circuit with a
truthful Pass ('file-based checks skipped') instead of the init warning.
Control vars like NETCLAW_HOME/NETCLAW_DAEMON_ENDPOINT don't count.
- ChatClientDoctorCheck: don't short-circuit at all in that state —
evaluate the BOUND configuration exactly like the daemon does at startup
(ProviderRuntimeConfiguration.FromConfiguration), so 'is a real provider
configured' is answered for env-only instances too.
- ConfigSchemaDoctorCheck: schema validation applies to the file only;
report that honestly instead of 'not configured'.
PersistTokenExpiry materialized a stub netclaw.json ({Providers:{name:
{OAuthTokenExpiry:...}}}) whenever a refresh returned an expiry and no
config file existed — silently turning an env-only deployment stateful,
and throwing from the refresh path on a read-only home directory.
Expiry is refresh-timing metadata, not required state (fresh configs ship
without it): update it when netclaw.json exists, skip it when it doesn't.
Secrets persistence (secrets.json) is unchanged.
Aaronontheweb
enabled auto-merge (squash)
July 3, 2026 17:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Investigated whether an instance configured solely via
NETCLAW_environment variables (nonetclaw.jsonon disk) is treated as uninitialized. The daemon core is correct: the JSON file loads asoptional: true, env vars bind over it, the #1540 degraded-startup gate evaluates the bound configuration, and the hot-reload watcher tolerates a missing file. An env-only daemon boots healthy (this is exactly how the containerized eval harness runs).Two adjacent components, however, conflate "no config file" with "not configured":
Fix 1 —
netclaw doctormisdiagnoses env-only instancesEvery check consulting
DoctorJsonConfigReader.TryReadConfigwarneddaemon not configured - please run netclaw initpurely on file absence — false alarm on a healthy env-only deployment.TryReadConfig: when the file is missing butNETCLAW_config-binding env vars are present (double-underscore section keys likeNETCLAW_Models__Main__ModelId; control vars likeNETCLAW_HOME/NETCLAW_DAEMON_ENDPOINTdon't count), checks short-circuit with a truthful Pass ("file-based checks skipped") instead of the init warning.ChatClientDoctorCheckgoes further: it evaluates the bound configuration exactly like the daemon does at startup (ProviderRuntimeConfiguration.FromConfiguration), so "is a real provider configured" gets a real answer for env-only instances.ConfigSchemaDoctorCheck: schema validation applies tonetclaw.jsonspecifically; says so honestly instead of "not configured".Genuinely unconfigured installs (no file, no env config) still get the existing init guidance.
Fix 2 — OAuth token refresh silently creates
netclaw.jsonOAuthTokenPersistence.PersistTokenExpirymaterialized a stub config file ({"Providers":{"<name>":{"OAuthTokenExpiry":…}}}) whenever a refresh returned an expiry and no file existed — an env-only deployment silently becomes stateful, and on a read-only home directory the write throws from inside the refresh path. Expiry is refresh-timing metadata, not required state (fresh configs ship without it): it's now updated when the file exists and skipped when it doesn't. Secrets persistence (secrets.json) is unchanged.Testing
HasEnvironmentConfigclassification (control vars excluded), OAuth refresh on env-only instance creates no file.netclaw.jsonlike init-based installs (they previously relied on the create-on-missing side effect).Netclaw.Cli.Tests(1,194) andNetclaw.Configuration.Tests(447) green; slopwatch clean; headers verified.Related
NETCLAW_DAEMON_ENDPOINT/paired clients). This PR covers the remaining env-only surfaces. Independent of both fix(cli): don't demand local daemon config when the daemon is explicitly remote #1567 and the memory PR (memory: July 2026 audit + quick wins — revive curation LLM, balanced prompt, recall precision re-tune #1568).