fix(code): surface /auth-stored credentials in config show/get (#4258)

`dcode config show`/`config get` now report API keys stored via `/auth`,
labeled with a `stored` source.

---

`dcode config show` and `config get` resolved credentials only through
environment variables and `config.toml`, so a key entered via `/auth`
(persisted in `auth.json`) rendered as `not configured` even though the
agent actually sends it at runtime. This made a real 401-causing trap
invisible: stored keys take precedence over env vars
(`resolve_provider_credential`), so a stale stored key silently
overrides a known-good `ANTHROPIC_API_KEY`, and the diagnostic command
gave no hint.

`_resolve` now consults the credential store first for `Credentials`
options, mirroring the runtime precedence, and reports a `stored` source
so the effective credential — and any precedence surprise — is visible.
A corrupt store is logged and treated as absent so the command degrades
to env/`config.toml` resolution instead of erroring.

Made by [Open
SWE](https://openswe.vercel.app/agents/cb055d71-831a-f788-02a0-308c83974f4d)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-06-25 20:30:21 -04:00
committed by GitHub
parent 37c5be9561
commit c7c8788ecf
5 changed files with 599 additions and 32 deletions
+5 -1
View File
@@ -144,7 +144,11 @@ def _read_raw() -> dict | None:
"Check the file permissions on the parent directory."
)
raise RuntimeError(msg) from exc
except json.JSONDecodeError as exc:
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
# `UnicodeDecodeError` (a `ValueError`, not an `OSError`) escapes the
# handler above when the file holds non-UTF-8 bytes; treat a decode
# failure as corruption so callers get the same `RuntimeError` hint
# instead of an unhandled traceback.
msg = (
f"Failed to parse credential file {path}: {exc}. "
"Delete the file and re-add credentials via /auth if it is corrupt."
+189 -31
View File
@@ -2,9 +2,10 @@
`config list` prints the static manifest (every tunable option, its type,
default, and where it can be set). `config show` resolves each option against
the live environment and `config.toml`, reporting the effective value and which
source provided it. `config get <key>` does the same for a single option.
`config path` prints the on-disk config locations.
the app credential store (for credentials), the live environment, and
`config.toml`, reporting the effective value and which source provided it.
`config get <key>` does the same for a single option. `config path` prints the
on-disk config locations.
Secret-flagged options (API keys and other credentials) are never printed by
value — `config show`/`config get` report only whether they are set and from
@@ -21,7 +22,9 @@ from __future__ import annotations
import importlib.util
import logging
import os
import sys
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
from deepagents_code.output import write_json
@@ -130,22 +133,121 @@ def setup_config_parser(
# --- Resolution -------------------------------------------------------------
def _resolve(option: ConfigOption, toml_data: dict[str, Any]) -> tuple[bool, str, Any]:
"""Resolve an option via the shared manifest resolver.
@dataclass(frozen=True, slots=True)
class _StoredCredentialView:
"""Snapshot of the `/auth` credential store for one command invocation.
Delegates to `config_manifest.resolve_scalar` so `config show`/`get`
report exactly what the runtime reads.
Built once per `config show`/`get` so the store is read and parsed a single
time rather than once per credential option.
"""
keys: dict[str, str] = field(repr=False)
"""Provider/service name to stored API key, for `api_key` entries only.
`repr=False` keeps the secret key values out of the dataclass repr, so an
accidental log/`%r` of the view can't leak them.
"""
error: str | None = None
"""Secret-free remediation message when the store was unreadable, else `None`.
Never holds the underlying exception text (which can echo file bytes) or any
key value.
"""
_STORE_UNREADABLE_HINT = (
"credential store unreadable; showing env/config.toml resolution instead. "
"Re-add keys via /auth (or delete a corrupt auth.json)."
)
"""Fixed, secret-free notice surfaced when `auth.json` cannot be read."""
def _load_stored_credentials() -> _StoredCredentialView:
"""Read every `/auth`-stored API key once, degrading a corrupt store to empty.
Reading the store a single time (rather than once per credential option)
keeps `config show` to one `auth.json` parse and one warning. A corrupt
store is logged once and reported via the returned `error`, so resolution
degrades to env/`config.toml` instead of failing the command — and the
corruption stays visible in the output rather than masquerading as an empty
store.
Returns:
A `_StoredCredentialView` whose `keys` map holds stored `api_key` values
by provider, and whose `error` is set only when the store was unreadable.
"""
from deepagents_code import auth_store
try:
creds = auth_store.load_credentials()
except RuntimeError:
# Omit the exception text on purpose: it can echo file contents, and the
# remediation is identical regardless of the specific parse failure.
logger.warning("Could not read stored credentials; treating as absent")
return _StoredCredentialView(keys={}, error=_STORE_UNREADABLE_HINT)
keys = {
provider: entry["key"]
for provider, entry in creds.items()
if entry["type"] == "api_key" and entry["key"]
}
return _StoredCredentialView(keys=keys)
def _resolve(
option: ConfigOption,
toml_data: dict[str, Any],
*,
stored: _StoredCredentialView | None = None,
) -> tuple[bool, str, Any]:
"""Resolve an option for display, reporting what the runtime actually reads.
Credential options follow runtime precedence: a present `DEEPAGENTS_CODE_`
env override wins (the model factory reads it via `resolve_env_var` even
after `apply_stored_credentials` bridges a stored key onto the canonical
var), then a key stored via `/auth`, then the canonical env/`config.toml`.
Everything else delegates straight to `config_manifest.resolve_scalar`.
Args:
option: The option to resolve.
toml_data: Parsed `config.toml` contents.
stored: Pre-loaded credential-store snapshot. When `None`, the store is
read on demand — fine for one-off calls, but callers resolving many
options should load it once and pass it so `auth.json` is parsed a
single time.
Returns:
`(is_set, source, value)`, where `is_set` is `False` when the value
came from the typed default.
"""
from deepagents_code.config_manifest import resolve_scalar
from deepagents_code.model_config import ProviderAuthSource
if (
option.group == "Credentials"
and option.provider is not None
and not _has_prefixed_env_override(option)
):
if stored is None:
stored = _load_stored_credentials()
key = stored.keys.get(option.provider)
if key is not None:
return True, ProviderAuthSource.STORED.value, key
value, source = resolve_scalar(option, toml_data=toml_data)
return source != "default", source, value
def _has_prefixed_env_override(option: ConfigOption) -> bool:
"""Return whether an option's `DEEPAGENTS_CODE_` env var is present."""
if option.env_var is None:
return False
prefix = "DEEPAGENTS_CODE_"
if option.env_var.startswith(prefix):
return False
return f"{prefix}{option.env_var}" in os.environ
def _display_value(option: ConfigOption, *, is_set: bool, value: object) -> str:
"""Render an option value for human output, redacting secrets.
@@ -172,15 +274,27 @@ def _display_value(option: ConfigOption, *, is_set: bool, value: object) -> str:
return text
def _source_label(source: str) -> str:
def _source_label(source: str, *, option: ConfigOption | None = None) -> str:
"""Render the source column for human output.
Returns:
Source label for the value's origin.
"""
if option is not None and option.group == "Credentials":
env = _env_source_name(source)
if env is not None and env.startswith("DEEPAGENTS_CODE_"):
return f"{source}; session override"
return source
def _env_source_name(source: str) -> str | None:
"""Return the env var name from an `env (...)` source label, if present."""
prefix = "env ("
if not source.startswith(prefix) or not source.endswith(")"):
return None
return source[len(prefix) : -1]
def _with_availability(option: ConfigOption, text: str) -> str:
"""Append provider availability to a credential display value when needed.
@@ -212,6 +326,36 @@ def _missing_extra_hint(option: ConfigOption) -> bool:
# --- Commands ---------------------------------------------------------------
def _show_json_row(
option: ConfigOption,
*,
is_set: bool,
source: str,
value: object,
store_error: str | None,
) -> dict[str, Any]:
"""Build one `config show --json` row, redacting secrets and flagging store errors.
Returns:
A JSON-serializable row. Redacted options report presence only (`value`
is `None`); a `store_error` key is added to credential rows when
the `/auth` store was unreadable, so a corrupt store is
distinguishable from an empty one in the bug-report artifact.
"""
row: dict[str, Any] = {
"key": option.key,
"group": option.group,
"source": source,
"set": is_set,
"redacted": option.redacted,
# Redact secret values: report presence only.
"value": None if option.redacted else value,
}
if store_error and option.group == "Credentials":
row["store_error"] = store_error
return row
def _run_show(output_format: OutputFormat) -> int:
"""Resolve every option and print its effective value and source.
@@ -225,23 +369,24 @@ def _run_show(output_format: OutputFormat) -> int:
# app actually reads, not just shell exports.
_ensure_bootstrap()
toml_data = load_config_toml()
# Read the credential store once; `_resolve` reuses this snapshot rather than
# re-parsing `auth.json` per credential option.
stored = _load_stored_credentials()
options = get_config_options()
resolved = [(opt, *_resolve(opt, toml_data)) for opt in options]
resolved = [(opt, *_resolve(opt, toml_data, stored=stored)) for opt in options]
if output_format == "json":
write_json(
"config show",
[
{
"key": opt.key,
"group": opt.group,
"source": source,
"set": is_set,
"redacted": opt.redacted,
# Redact secret values: report presence only.
"value": None if opt.redacted else value,
}
_show_json_row(
opt,
is_set=is_set,
source=source,
value=value,
store_error=stored.error,
)
for opt, is_set, source, value in resolved
],
)
@@ -253,13 +398,18 @@ def _run_show(output_format: OutputFormat) -> int:
from deepagents_code.config_manifest import iter_groups
console.print()
if stored.error:
console.print(
f"[yellow]Warning:[/yellow] {escape(stored.error)}", highlight=False
)
console.print()
for group in iter_groups(options):
console.print(f"[bold]{group}[/bold]")
for opt, is_set, source, value in resolved:
if opt.group != group:
continue
display = _display_value(opt, is_set=is_set, value=value)
source_label = _source_label(source)
source_label = _source_label(source, option=opt)
# `display` and `source_label` may contain Rich markup from env/TOML
# or terminal metadata; escape them so values can't break rendering.
display_text = escape(display)
@@ -342,19 +492,23 @@ def _run_get(key: str, output_format: OutputFormat) -> int:
_ensure_bootstrap()
toml_data = load_config_toml()
is_set, source, value = _resolve(option, toml_data)
# Only credential options consult the store, so skip the read (and its
# warning) for everything else.
stored = _load_stored_credentials() if option.group == "Credentials" else None
is_set, source, value = _resolve(option, toml_data, stored=stored)
store_error = stored.error if stored is not None else None
if output_format == "json":
write_json(
"config get",
{
"key": option.key,
"source": source,
"set": is_set,
"redacted": option.redacted,
"value": None if option.redacted else value,
},
)
payload: dict[str, Any] = {
"key": option.key,
"source": source,
"set": is_set,
"redacted": option.redacted,
"value": None if option.redacted else value,
}
if store_error:
payload["store_error"] = store_error
write_json("config get", payload)
return 0
from rich.markup import escape
@@ -362,11 +516,15 @@ def _run_get(key: str, output_format: OutputFormat) -> int:
from deepagents_code.config import console
display = _display_value(option, is_set=is_set, value=value)
source_label = _source_label(source)
source_label = _source_label(source, option=option)
console.print(
f"{option.key} = {escape(display)} [dim]({escape(source_label)})[/dim]",
highlight=False,
)
if store_error:
console.print(
f"[yellow]Warning:[/yellow] {escape(store_error)}", highlight=False
)
return 0
@@ -210,6 +210,16 @@ class ConfigOption:
install_extra: str | None = None
"""Optional `deepagents-code[...]` extra that provides `dependency_module`."""
provider: str | None = None
"""Provider/service name a credential option authenticates, or `None`.
Set only for `Credentials`-group options (e.g. `"anthropic"`, `"tavily"`),
where it is the key `/auth` stores the credential under and the name passed
to `model_config.is_service`. Carrying it as a structured field lets
`config show`/`get` look up the stored credential without re-parsing it out
of `key`. `None` for every other option.
"""
def __post_init__(self) -> None:
"""Reject a `default` that contradicts `kind` at construction time.
@@ -740,6 +750,7 @@ def _credential_options() -> tuple[ConfigOption, ...]:
kind=OptionKind.STR,
env_var=env_var,
redacted=redacted,
provider=name,
settings_field=_CREDENTIAL_SETTINGS_FIELD.get(env_var),
dependency_module=dependency[0] if dependency else None,
install_extra=dependency[1] if dependency else None,
@@ -264,6 +264,14 @@ class TestCorruption:
with pytest.raises(RuntimeError, match="Delete the file"):
auth_store.load_credentials()
def test_non_utf8_raises(self, fake_home: Path) -> None:
"""A non-UTF-8 file surfaces the deletion hint, not a `UnicodeDecodeError`."""
path = _auth_file(fake_home)
path.parent.mkdir(parents=True)
path.write_bytes(b"\xff\xfe not utf-8")
with pytest.raises(RuntimeError, match="Delete the file"):
auth_store.load_credentials()
def test_unknown_version_raises(self, fake_home: Path) -> None:
"""A future schema version is rejected."""
path = _auth_file(fake_home)
@@ -194,6 +194,30 @@ def test_missing_extra_hint_checks_provider_dependency(monkeypatch) -> None:
assert _source_label("default") == "default"
def test_source_label_marks_prefixed_credential_env_as_session_override() -> None:
"""Only prefixed credential env sources get the session-override note."""
credential = get_option("credentials.anthropic")
assert credential is not None
display = get_option("display.theme")
assert display is not None
assert (
_source_label(
"env (DEEPAGENTS_CODE_ANTHROPIC_API_KEY)",
option=credential,
)
== "env (DEEPAGENTS_CODE_ANTHROPIC_API_KEY); session override"
)
assert (
_source_label("env (ANTHROPIC_API_KEY)", option=credential)
== "env (ANTHROPIC_API_KEY)"
)
assert (
_source_label("env (DEEPAGENTS_CODE_THEME)", option=display)
== "env (DEEPAGENTS_CODE_THEME)"
)
def test_run_get_json_omits_secret_value(monkeypatch, capsys) -> None:
"""JSON output for a secret option reports presence but never the value."""
import json
@@ -205,6 +229,368 @@ def test_run_get_json_omits_secret_value(monkeypatch, capsys) -> None:
assert payload["data"]["value"] is None
@pytest.fixture
def stored_auth_dir(tmp_path, monkeypatch):
"""Redirect the credential store into a temp dir so `/auth` keys are isolated."""
state_dir = tmp_path / ".deepagents" / ".state"
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_STATE_DIR", state_dir)
return state_dir
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_credential_prefers_stored_over_env(monkeypatch):
"""A `/auth`-stored key wins over an env var, matching runtime precedence."""
from deepagents_code import auth_store
monkeypatch.setenv("ANTHROPIC_API_KEY", "from-env")
auth_store.set_stored_key("anthropic", "from-store")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "stored"
assert value == "from-store"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_credential_prefers_prefixed_env_over_stored(monkeypatch):
"""A prefixed credential env var stays authoritative over a stored key."""
from deepagents_code import auth_store
monkeypatch.setenv("DEEPAGENTS_CODE_ANTHROPIC_API_KEY", "from-prefix")
auth_store.set_stored_key("anthropic", "from-store")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "env (DEEPAGENTS_CODE_ANTHROPIC_API_KEY)"
assert value == "from-prefix"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_empty_prefixed_credential_blocks_stored(monkeypatch):
"""An empty prefixed credential suppresses the stored key like the runtime."""
from deepagents_code import auth_store
monkeypatch.setenv("DEEPAGENTS_CODE_ANTHROPIC_API_KEY", "")
auth_store.set_stored_key("anthropic", "from-store")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is False
assert source == "default"
assert value is None
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_credential_uses_stored_when_env_unset(monkeypatch):
"""A stored key is surfaced even with no env var set."""
from deepagents_code import auth_store
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("DEEPAGENTS_CODE_ANTHROPIC_API_KEY", raising=False)
auth_store.set_stored_key("anthropic", "from-store")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, _ = _resolve(option, {})
assert is_set is True
assert source == "stored"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_credential_falls_back_to_env_without_stored(monkeypatch):
"""With no stored key, resolution falls through to the env var."""
monkeypatch.setenv("ANTHROPIC_API_KEY", "from-env")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "env (ANTHROPIC_API_KEY)"
assert value == "from-env"
def test_resolve_credential_corrupt_store_falls_back_to_env(
stored_auth_dir, monkeypatch, caplog
):
"""A corrupt `auth.json` is logged and treated as absent, not a hard error."""
import logging
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text("{ not json", encoding="utf-8")
monkeypatch.setenv("ANTHROPIC_API_KEY", "from-env")
option = get_option("credentials.anthropic")
assert option is not None
with caplog.at_level(logging.WARNING):
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "env (ANTHROPIC_API_KEY)"
assert value == "from-env"
assert "treating as absent" in caplog.text
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_get_text_reports_stored_source(capsys):
"""`config get` shows a stored credential as configured from the store."""
from deepagents_code import auth_store
auth_store.set_stored_key("anthropic", "from-store")
assert _run_get("credentials.anthropic", "text") == 0
out = capsys.readouterr().out
assert "configured" in out
assert "stored" in out
assert "from-store" not in out
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_get_text_reports_prefixed_env_as_session_override(monkeypatch, capsys):
"""`config get` labels a visible prefixed credential env var as session-scoped."""
from deepagents_code import auth_store
monkeypatch.setenv("DEEPAGENTS_CODE_ANTHROPIC_API_KEY", "from-prefix")
auth_store.set_stored_key("anthropic", "from-store")
assert _run_get("credentials.anthropic", "text") == 0
out = capsys.readouterr().out
compact = " ".join(out.split())
assert "configured" in out
assert "env (DEEPAGENTS_CODE_ANTHROPIC_API_KEY); session override" in compact
assert "from-prefix" not in out
assert "from-store" not in out
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_get_json_redacts_stored_secret_value(capsys):
"""`config get --json` reports a stored credential as set but never its value."""
import json
from deepagents_code import auth_store
auth_store.set_stored_key("anthropic", "from-store")
assert _run_get("credentials.anthropic", "json") == 0
raw = capsys.readouterr().out
payload = json.loads(raw)["data"]
assert payload["set"] is True
assert payload["source"] == "stored"
assert payload["value"] is None
assert "from-store" not in raw
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_show_json_redacts_stored_secret_value(capsys):
"""`config show --json` redacts a stored secret on the aggregate path too."""
import json
from deepagents_code import auth_store
auth_store.set_stored_key("anthropic", "from-store")
args = argparse.Namespace(config_command="show", output_format="json")
assert run_config_command(args) == 0
raw = capsys.readouterr().out
rows = json.loads(raw)["data"]
row = next(r for r in rows if r["key"] == "credentials.anthropic")
assert row["set"] is True
assert row["source"] == "stored"
assert row["value"] is None
assert "from-store" not in raw
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_show_text_reports_stored_source(capsys):
"""`config show` (aggregate text path) shows a stored credential as configured."""
from deepagents_code import auth_store
auth_store.set_stored_key("anthropic", "from-store")
args = argparse.Namespace(config_command="show", output_format="text")
assert run_config_command(args) == 0
out = capsys.readouterr().out
assert "configured" in out
assert "stored" in out
assert "from-store" not in out
def test_resolve_empty_stored_key_falls_back_to_env(stored_auth_dir, monkeypatch):
"""A stored entry with a blank key does not mask a working env var."""
import json
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text(
json.dumps(
{
"version": 1,
"credentials": {
"anthropic": {"type": "api_key", "key": "", "added_at": ""}
},
}
),
encoding="utf-8",
)
monkeypatch.setenv("ANTHROPIC_API_KEY", "from-env")
option = get_option("credentials.anthropic")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "env (ANTHROPIC_API_KEY)"
assert value == "from-env"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_non_credential_ignores_store():
"""Non-credential options never consult the `/auth` store."""
from deepagents_code import auth_store
auth_store.set_stored_key("anthropic", "from-store")
option = get_option("display.show_header")
assert option is not None
_, source, _ = _resolve(option, {})
assert source != "stored"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_tavily_service_prefers_stored(monkeypatch):
"""A stored key for the tavily *service* resolves with a stored source.
Guards that the credential branch keys on group membership (and the
`provider` field), not on model-provider-registry membership.
"""
from deepagents_code import auth_store
monkeypatch.delenv("TAVILY_API_KEY", raising=False)
monkeypatch.delenv("DEEPAGENTS_CODE_TAVILY_API_KEY", raising=False)
auth_store.set_stored_key("tavily", "from-store")
option = get_option("credentials.tavily")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "stored"
assert value == "from-store"
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_tavily_prefixed_env_overrides_stored(monkeypatch):
"""A prefixed tavily env var wins over the stored key, matching runtime."""
from deepagents_code import auth_store
monkeypatch.setenv("DEEPAGENTS_CODE_TAVILY_API_KEY", "from-prefix")
auth_store.set_stored_key("tavily", "from-store")
option = get_option("credentials.tavily")
assert option is not None
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "env (DEEPAGENTS_CODE_TAVILY_API_KEY)"
assert value == "from-prefix"
def test_run_get_json_flags_unreadable_store(stored_auth_dir, capsys):
"""`config get --json` for a credential surfaces a store-read failure in-band."""
import json
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text("{ not json", encoding="utf-8")
assert _run_get("credentials.anthropic", "json") == 0
payload = json.loads(capsys.readouterr().out)["data"]
assert "store_error" in payload
# Redaction still holds even when the store is unreadable.
assert payload["value"] is None
def test_run_get_json_non_credential_omits_store_error(stored_auth_dir, capsys):
"""A non-credential `config get --json` never carries a `store_error` key."""
import json
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text("{ not json", encoding="utf-8")
assert _run_get("display.show_header", "json") == 0
payload = json.loads(capsys.readouterr().out)["data"]
assert "store_error" not in payload
def test_run_show_json_flags_unreadable_store(stored_auth_dir, capsys):
"""`config show --json` marks credential rows when the store is unreadable."""
import json
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text("{ not json", encoding="utf-8")
args = argparse.Namespace(config_command="show", output_format="json")
assert run_config_command(args) == 0
rows = json.loads(capsys.readouterr().out)["data"]
cred_rows = [r for r in rows if r["group"] == "Credentials"]
assert cred_rows
assert all("store_error" in r for r in cred_rows)
assert all("store_error" not in r for r in rows if r["group"] != "Credentials")
def test_run_get_text_warns_on_unreadable_store(stored_auth_dir, capsys):
"""`config get` text output shows a warning banner for an unreadable store."""
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_text("{ not json", encoding="utf-8")
assert _run_get("credentials.anthropic", "text") == 0
out = capsys.readouterr().out
assert "Warning" in out
assert "unreadable" in out
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_show_reads_store_once(monkeypatch):
"""`config show` parses the credential store once, not once per option."""
from deepagents_code import auth_store
calls = 0
real_load = auth_store.load_credentials
def _counting_load() -> dict:
nonlocal calls
calls += 1
return real_load()
monkeypatch.setattr(auth_store, "load_credentials", _counting_load)
args = argparse.Namespace(config_command="show", output_format="json")
assert run_config_command(args) == 0
# One read for the whole command, regardless of how many credential options
# exist — guards the single-snapshot design against a per-option regression.
assert calls == 1
@pytest.mark.usefixtures("stored_auth_dir")
def test_resolve_non_redacted_credential_shows_stored_value(monkeypatch):
"""A non-redacted stored credential (the Vertex project) shows its value."""
from deepagents_code import auth_store
monkeypatch.delenv("GOOGLE_CLOUD_PROJECT", raising=False)
monkeypatch.delenv("DEEPAGENTS_CODE_GOOGLE_CLOUD_PROJECT", raising=False)
auth_store.set_stored_key("google_vertexai", "my-project")
option = get_option("credentials.google_vertexai")
assert option is not None
assert option.redacted is False
is_set, source, value = _resolve(option, {})
assert is_set is True
assert source == "stored"
assert value == "my-project"
@pytest.mark.usefixtures("stored_auth_dir")
def test_run_get_json_shows_non_redacted_stored_value(capsys):
"""`config get --json` surfaces a non-redacted stored value (not `None`)."""
import json
from deepagents_code import auth_store
auth_store.set_stored_key("google_vertexai", "my-project")
assert _run_get("credentials.google_vertexai", "json") == 0
payload = json.loads(capsys.readouterr().out)["data"]
assert payload["source"] == "stored"
assert payload["redacted"] is False
assert payload["value"] == "my-project"
def test_run_get_non_utf8_store_does_not_crash(stored_auth_dir, capsys):
"""A non-UTF-8 `auth.json` degrades to a warning banner, not a traceback."""
stored_auth_dir.mkdir(parents=True, exist_ok=True)
(stored_auth_dir / "auth.json").write_bytes(b"\xff\xfe not utf-8")
assert _run_get("credentials.anthropic", "text") == 0
out = capsys.readouterr().out
assert "Warning" in out
assert "unreadable" in out
def test_charset_auto_display_value_includes_effective_glyph_mode() -> None:
"""The charset auto value says which glyph mode is actually being used."""
option = get_option("display.charset")