mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(auth): hermes auth remove sticks for shell-exported env vars (#13418)
Removing an env-seeded credential only cleared ~/.hermes/.env and the current process's os.environ, leaving shell-exported vars (shell profile, systemd EnvironmentFile, launchd plist) to resurrect the entry on the next load_pool() call. This matched the pre-#11485 codex behaviour. Now we suppress env:<VAR> in auth.json on remove, gate _seed_from_env() behind is_source_suppressed(), clear env:* suppressions on auth add, and print a diagnostic pointing at the shell when the var lives there. Applies to every env:* seeded credential (xai, deepseek, moonshot, zai, nvidia, openrouter, anthropic, etc.), not just xai. Reported by @teknium1 from community user 'Artificial Brain' — couldn't remove their xAI key via hermes auth remove.
This commit is contained in:
parent
26abac5afd
commit
b341b19fff
3 changed files with 248 additions and 2 deletions
|
|
@ -152,6 +152,22 @@ def auth_add_command(args) -> None:
|
|||
|
||||
pool = load_pool(provider)
|
||||
|
||||
# Clear any env:<VAR> suppressions for this provider — re-adding a
|
||||
# credential is a strong signal the user wants auth for this provider
|
||||
# re-enabled. Matches the Codex device_code re-link pattern below.
|
||||
if not provider.startswith(CUSTOM_POOL_PREFIX):
|
||||
try:
|
||||
from hermes_cli.auth import (
|
||||
_load_auth_store,
|
||||
unsuppress_credential_source,
|
||||
)
|
||||
suppressed = _load_auth_store().get("suppressed_sources", {})
|
||||
for src in list(suppressed.get(provider, []) or []):
|
||||
if src.startswith("env:"):
|
||||
unsuppress_credential_source(provider, src)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if requested_type == AUTH_TYPE_API_KEY:
|
||||
token = (getattr(args, "api_key", None) or "").strip()
|
||||
if not token:
|
||||
|
|
@ -339,14 +355,56 @@ def auth_remove_command(args) -> None:
|
|||
print(f"Removed {provider} credential #{index} ({removed.label})")
|
||||
|
||||
# If this was an env-seeded credential, also clear the env var from .env
|
||||
# so it doesn't get re-seeded on the next load_pool() call.
|
||||
# so it doesn't get re-seeded on the next load_pool() call. If the env
|
||||
# var is also (or only) exported by the user's shell/systemd, .env
|
||||
# cleanup alone is not enough — the next process to call load_pool()
|
||||
# will re-read os.environ and resurrect the entry. Suppress the
|
||||
# env:<VAR> source so _seed_from_env() skips it, and tell the user
|
||||
# where the shell-level copy is still living so they can remove it.
|
||||
if removed.source.startswith("env:"):
|
||||
import os as _os
|
||||
env_var = removed.source[len("env:"):]
|
||||
if env_var:
|
||||
from hermes_cli.config import remove_env_value
|
||||
from hermes_cli.config import get_env_path, remove_env_value
|
||||
from hermes_cli.auth import suppress_credential_source
|
||||
|
||||
# Detect whether the var lives in .env, the shell env, or both,
|
||||
# BEFORE remove_env_value() mutates os.environ.
|
||||
env_in_process = bool(_os.getenv(env_var))
|
||||
env_in_dotenv = False
|
||||
try:
|
||||
env_path = get_env_path()
|
||||
if env_path.exists():
|
||||
env_in_dotenv = any(
|
||||
line.strip().startswith(f"{env_var}=")
|
||||
for line in env_path.read_text(errors="replace").splitlines()
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
shell_exported = env_in_process and not env_in_dotenv
|
||||
|
||||
cleared = remove_env_value(env_var)
|
||||
if cleared:
|
||||
print(f"Cleared {env_var} from .env")
|
||||
suppress_credential_source(provider, removed.source)
|
||||
if shell_exported:
|
||||
print(
|
||||
f"Note: {env_var} is still set in your shell environment "
|
||||
f"(not in ~/.hermes/.env)."
|
||||
)
|
||||
print(
|
||||
" Unset it there (shell profile, systemd EnvironmentFile, "
|
||||
"launchd plist, etc.) or it will keep being visible to Hermes."
|
||||
)
|
||||
print(
|
||||
f" The pool entry is now suppressed — Hermes will ignore "
|
||||
f"{env_var} until you run `hermes auth add {provider}`."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Suppressed env:{env_var} — it will not be re-seeded even "
|
||||
f"if the variable is re-exported later."
|
||||
)
|
||||
|
||||
# If this was a singleton-seeded credential (OAuth device_code, hermes_pkce),
|
||||
# clear the underlying auth store / credential file so it doesn't get
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue