Skip to content

fix: support environment-variable-only configuration (doctor misdiagnosis + OAuth config-file side effect) - #1569

Merged
Aaronontheweb merged 4 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/env-only-config-support
Jul 3, 2026
Merged

fix: support environment-variable-only configuration (doctor misdiagnosis + OAuth config-file side effect)#1569
Aaronontheweb merged 4 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/env-only-config-support

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Context

Investigated whether an instance configured solely via NETCLAW_ environment variables (no netclaw.json on disk) is treated as uninitialized. The daemon core is correct: the JSON file loads as optional: 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 doctor misdiagnoses env-only instances

Every check consulting DoctorJsonConfigReader.TryReadConfig warned daemon not configured - please run netclaw init purely on file absence — false alarm on a healthy env-only deployment.

  • TryReadConfig: when the file is missing but NETCLAW_ config-binding env vars are present (double-underscore section keys like NETCLAW_Models__Main__ModelId; control vars like NETCLAW_HOME/NETCLAW_DAEMON_ENDPOINT don't count), checks short-circuit with a truthful Pass ("file-based checks skipped") instead of the init warning.
  • ChatClientDoctorCheck goes 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 to netclaw.json specifically; 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.json

OAuthTokenPersistence.PersistTokenExpiry materialized 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

  • New: env-only ChatClient evaluation (Pass with real provider verdict), env-only ConfigSchema (truthful Pass), HasEnvironmentConfig classification (control vars excluded), OAuth refresh on env-only instance creates no file.
  • Updated: OAuth persistence fixtures now seed netclaw.json like init-based installs (they previously relied on the create-on-missing side effect).
  • Full Netclaw.Cli.Tests (1,194) and Netclaw.Configuration.Tests (447) green; slopwatch clean; headers verified.

Related

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 Aaronontheweb added bug Something isn't working config Configuration issues, netclaw doctor, schema validation. labels Jul 3, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) July 3, 2026 17:59
@Aaronontheweb
Aaronontheweb merged commit c5b0779 into netclaw-dev:dev Jul 3, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working config Configuration issues, netclaw doctor, schema validation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant