Skip to content

fix(credentials): unified provider key delete/update across .env, auth.json, config.yaml - #67213

Merged
teknium1 merged 2 commits into
mainfrom
lane/c5-cred-lifecycle
Jul 19, 2026
Merged

fix(credentials): unified provider key delete/update across .env, auth.json, config.yaml#67213
teknium1 merged 2 commits into
mainfrom
lane/c5-cred-lifecycle

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Provider API key delete/update now goes through one unified lifecycle helper that keeps all three credential stores consistent — .env, auth.json credential_pool, and config.yaml — fixing the class of "removed the key but the provider is still there" / "updated the key but the old one still authenticates" bugs. Plus: .env lines written with an export prefix no longer 500 the save/remove endpoints.

Root causes:

Changes

  • hermes_cli/credential_lifecycle.py (new): unified delete/update — purges env-seeded pool entries + config.yaml mirrors on delete, clears stale higher-precedence copies on update; OAuth grants for the same provider are preserved (entry types distinguished)
  • hermes_cli/web_server.py + tui_gateway/server.py: env endpoints route through the helper; response fields additive
  • hermes_cli/config.py: export-prefixed .env line handling in save/remove

Validation

  • E2E-style tests against temp HERMES_HOME with real .env + auth.json + config.yaml fixtures: RED captured for each symptom, GREEN after (335 + 125 test lines)
  • ghp_* PAT save/remove regression test
  • 3 remaining local failures verified pre-existing on origin/main (unrelated tui_resume_flow/PtyWebSocket family)

Closes #51071. Closes #59761. Closes #62269. Closes #40041.

Infographic

credential-lifecycle

teknium1 added 2 commits July 18, 2026 16:26
load_env() parses bash-compatible 'export KEY=value' lines (#6659), so a
hand-added 'export GITHUB_TOKEN=ghp_...' shows as set (green light) in the
desktop Tools & Keys page. But save_env_value/remove_env_value only matched
plain 'KEY=' lines:

- DELETE /api/env 404'd ('not found in .env') — the token could not be
  removed through the UI
- PUT /api/env appended a SECOND line; a later delete removed the new line
  while the export line silently resurrected the old value

Both writers now match assignments through a shared _env_line_defines_key()
helper that understands the export prefix. Commented-out lines are still
ignored.

Regression tests drive the real dashboard endpoint handlers against a temp
HERMES_HOME with runtime-constructed classic-PAT-shaped fixtures, covering
save-does-not-500, export-line remove, export-line replace-without-duplicate,
and the plain-line path staying intact.

Fixes #40041
…json, config.yaml (#51071 #59761 #62269)

A provider API key can live in three stores at once: ~/.hermes/.env,
auth.json credential_pool (env-seeded 'env:<VAR>' entries persisted by the
pool loader), and config.yaml mirrors (model.api_key, auxiliary.*.api_key,
custom_providers[*].api_key). The desktop/dashboard endpoints and the TUI
gateway RPCs only ever mutated .env, so the stores diverged:

- #51071/#59761: DELETE /api/env removed the key from .env but left the
  credential_pool entry (the loader is additive-only and never prunes),
  so the provider kept appearing in the model picker — surviving restart
  via the stale pool entry + provider_models_cache.json row.
- #62269: PUT /api/env rewrote .env but left the OLD key in config.yaml
  (model.api_key wins over env at client construction), producing 401s
  with a key the UI no longer showed.

New hermes_cli/credential_lifecycle.py is the single choke point:

- remove_provider_env_credential(): clears the .env entry, prunes
  env:<VAR> pool entries across ALL providers (a shared var like
  GITHUB_TOKEN can seed several), suppresses the env source so a lingering
  shell export can't re-seed it (matching 'hermes auth remove' semantics),
  drops the affected providers' model-cache rows, and scrubs value-matched
  config.yaml api_key mirrors. Returns 'found' spanning every store so a
  stale pool-only entry is cleanable through the same delete button.
- save_provider_env_credential(): writes .env, rotates any config.yaml
  mirror that held the PREVIOUS value (value-matched — an unrelated inline
  key is untouched), and lifts a prior env-source suppression so re-adding
  behaves like 'hermes auth add'.

OAuth preservation: only entries with source == 'env:<VAR>' are pruned.
OAuth/device-code/manual/borrowed pool entries and providers.<id> OAuth
token blocks are never touched by a key-only delete. (model.disconnect in
the TUI gateway still clears OAuth via clear_provider_auth — that surface
is a full provider disconnect, which is the documented intent there.)

Rerouted call sites: PUT/DELETE /api/env (dashboard + desktop),
tui_gateway model.save_key / model.disconnect, save_env_value_secure
(TUI/gateway secret capture), and hermes config set/unset for env-shaped
keys.

E2E tests drive the real endpoint handlers against temp-HERMES_HOME
fixtures (.env + auth.json + config.yaml with runtime-constructed fake
keys) and assert cross-store consistency after delete/update, pool-reload
survival ('restart'), OAuth preservation, models-cache invalidation, and
the suppress/unsuppress round-trip.

Fixes #51071
Fixes #59761
Fixes #62269
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 19, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #51071, #59761, #62269, #40041, and #62467. This focused lifecycle repair reconciles persistent credentials across stores; it is complementary to the broader credential-resolver work.

@teknium1
teknium1 merged commit 9a987f1 into main Jul 19, 2026
33 checks passed
@teknium1
teknium1 deleted the lane/c5-cred-lifecycle branch July 19, 2026 10:02
@teknium1 teknium1 added the area/install-update Installer, updater, packaging, wheels, doctor label Jul 19, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…h.json, config.yaml (NousResearch#67213)

* fix(env): recognize export-prefixed .env lines in save/remove (NousResearch#40041)

load_env() parses bash-compatible 'export KEY=value' lines (NousResearch#6659), so a
hand-added 'export GITHUB_TOKEN=ghp_...' shows as set (green light) in the
desktop Tools & Keys page. But save_env_value/remove_env_value only matched
plain 'KEY=' lines:

- DELETE /api/env 404'd ('not found in .env') — the token could not be
  removed through the UI
- PUT /api/env appended a SECOND line; a later delete removed the new line
  while the export line silently resurrected the old value

Both writers now match assignments through a shared _env_line_defines_key()
helper that understands the export prefix. Commented-out lines are still
ignored.

Regression tests drive the real dashboard endpoint handlers against a temp
HERMES_HOME with runtime-constructed classic-PAT-shaped fixtures, covering
save-does-not-500, export-line remove, export-line replace-without-duplicate,
and the plain-line path staying intact.

Fixes NousResearch#40041

* fix(credentials): unify provider key delete/update across .env, auth.json, config.yaml (NousResearch#51071 NousResearch#59761 NousResearch#62269)

A provider API key can live in three stores at once: ~/.hermes/.env,
auth.json credential_pool (env-seeded 'env:<VAR>' entries persisted by the
pool loader), and config.yaml mirrors (model.api_key, auxiliary.*.api_key,
custom_providers[*].api_key). The desktop/dashboard endpoints and the TUI
gateway RPCs only ever mutated .env, so the stores diverged:

- NousResearch#51071/NousResearch#59761: DELETE /api/env removed the key from .env but left the
  credential_pool entry (the loader is additive-only and never prunes),
  so the provider kept appearing in the model picker — surviving restart
  via the stale pool entry + provider_models_cache.json row.
- NousResearch#62269: PUT /api/env rewrote .env but left the OLD key in config.yaml
  (model.api_key wins over env at client construction), producing 401s
  with a key the UI no longer showed.

New hermes_cli/credential_lifecycle.py is the single choke point:

- remove_provider_env_credential(): clears the .env entry, prunes
  env:<VAR> pool entries across ALL providers (a shared var like
  GITHUB_TOKEN can seed several), suppresses the env source so a lingering
  shell export can't re-seed it (matching 'hermes auth remove' semantics),
  drops the affected providers' model-cache rows, and scrubs value-matched
  config.yaml api_key mirrors. Returns 'found' spanning every store so a
  stale pool-only entry is cleanable through the same delete button.
- save_provider_env_credential(): writes .env, rotates any config.yaml
  mirror that held the PREVIOUS value (value-matched — an unrelated inline
  key is untouched), and lifts a prior env-source suppression so re-adding
  behaves like 'hermes auth add'.

OAuth preservation: only entries with source == 'env:<VAR>' are pruned.
OAuth/device-code/manual/borrowed pool entries and providers.<id> OAuth
token blocks are never touched by a key-only delete. (model.disconnect in
the TUI gateway still clears OAuth via clear_provider_auth — that surface
is a full provider disconnect, which is the documented intent there.)

Rerouted call sites: PUT/DELETE /api/env (dashboard + desktop),
tui_gateway model.save_key / model.disconnect, save_env_value_secure
(TUI/gateway secret capture), and hermes config set/unset for env-shaped
keys.

E2E tests drive the real endpoint handlers against temp-HERMES_HOME
fixtures (.env + auth.json + config.yaml with runtime-constructed fake
keys) and assert cross-store consistency after delete/update, pool-reload
survival ('restart'), OAuth preservation, models-cache invalidation, and
the suppress/unsuppress round-trip.

Fixes NousResearch#51071
Fixes NousResearch#59761
Fixes NousResearch#62269
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

2 participants