Skip to content

Commit bb9fb1b

Browse files
johnteeeclaude
andcommitted
fix: TASK-005 review F-C/F-D — redact webhook URL in doctor config; tolerate malformed numeric env
Closes the two remaining documented residuals from the doctor-config review. F-C: automation_webhook_url's value is now redacted in the `doctor config` view (a webhook URL can embed a token in its path/query). Scoped to this command via a local credential-endpoint set — the global redaction policy is unchanged — and the source/provenance is still shown. F-D: a malformed numeric env var (e.g. TEAAGENT_DAILY_COST_CAP_CENTS=abc) is now ignored in resolve_config_provenance instead of raising. The override falls back to the prior layer (default/config) rather than crashing every command, and the retained source makes the no-op visible in `doctor config`. Because load_workspace_defaults is derived from the resolver, it is hardened too (the old code crashed here). - teaagent/ergonomics/workspace_defaults.py: try/except around numeric coercion. - teaagent/cli/_handlers/_doctor.py: redact automation_webhook_url value. - Tests: malformed-env fallback (resolver + load_workspace_defaults); webhook-URL value redacted with source preserved. - Spec: all review findings marked resolved. Constraint: F-C redaction is command-scoped (no global policy change); F-D changes malformed-input handling from crash to graceful fallback (no change to valid-input behavior); load_workspace_defaults valid-value output unchanged. Tested: tests/test_workspace_defaults_toml.py 25 passed (incl. new F-C/F-D tests); mypy clean. Not-tested: full suite not run on 3.12 (hypothesis missing in 3.14 sandbox). Confidence: high Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 15db92e commit bb9fb1b

5 files changed

Lines changed: 72 additions & 9 deletions

File tree

docs/generated/docs-inventory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ Do not edit this file manually — regenerate instead.
562562
| `strategy/agent-ecosystem-core-values-2026-06-05.md` | archive | 12861 | `23194f9dc611` |
563563
| `strategy/competitive-analysis-and-positioning-2026-06-06.md` | archive | 67553 | `7c78b145750a` |
564564
| `strategy/daily-driver-roadmap-rationale-2026-06-04.md` | archive | 5053 | `3b6cdeed6d2e` |
565-
| `strategy/harness-first-direction-2026-06-13.md` | constitution | 20826 | `b4a9469900b8` |
565+
| `strategy/harness-first-direction-2026-06-13.md` | constitution | 21044 | `b04cf7200ba3` |
566566
| `strategy/implementation-roadmap-with-effort-impact-2026-06-06.md` | archive | 58270 | `fb0af11ee7a6` |
567567
| `strategy/malleable-governed-agent-harness-2026-06-03.md` | archive | 15446 | `eccbc0177490` |
568568
| `strategy/phase-0-to-phase-1-outlook-2026-06-04.md` | archive | 5314 | `374a502902b6` |

docs/strategy/harness-first-direction-2026-06-13.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,14 @@ spine-wide.
320320
- **Review fix (F-A):** provenance distinguishes the shell environment
321321
(`env:VAR`) from the workspace env file (`env-file:.teaagent/env`); a var set
322322
in both is attributed to the shell, which wins. (Initially both were
323-
mislabeled `env:` — defeating the point for env-file values.) Residuals,
324-
documented not fixed: `automation_webhook_url` is not redacted (could embed a
325-
token; consistent with existing redaction policy); a malformed numeric env var
326-
raises in both `resolve_config_provenance` and `load_workspace_defaults`
327-
(pre-existing, unchanged behavior).
323+
mislabeled `env:` — defeating the point for env-file values.)
324+
- **Review fixes (F-C, F-D):** `automation_webhook_url` value is now redacted in
325+
the `doctor config` view (it can embed a token), scoped to that command so the
326+
global redaction policy is unchanged; its source is still shown. A malformed
327+
numeric env var (e.g. `TEAAGENT_DAILY_COST_CAP_CENTS=abc`) is now ignored —
328+
the override falls back to the prior layer instead of crashing every command,
329+
and the retained `default`/`config` source makes the no-op visible. All review
330+
findings resolved.
328331

329332
### TASK-006: RunEvent taxonomy ADR + M0 dual-write spike
330333
- Goal: ADR for event schema; spine emitting alongside audit on the proof scenario.

teaagent/cli/_handlers/_doctor.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,12 @@ def doctor_config(args: argparse.Namespace) -> int:
382382
root = Path(getattr(args, 'root', '.')).resolve()
383383
prov = resolve_config_provenance(root)
384384

385+
# Credential-bearing endpoints whose VALUE is redacted in this view even
386+
# though the key name is not a generic secret marker (a webhook URL can
387+
# embed a token in its path/query). Scoped to this command — not a change to
388+
# the global redaction policy. Source/provenance is still shown.
389+
_credential_endpoint_keys = {'automation_webhook_url'}
390+
385391
# A list of entries (not a dict keyed by config key): the doctor JSON sink
386392
# redacts dict VALUES whose KEY is sensitive, which would collapse a
387393
# secret-keyed entry to a string and lose its provenance. Keying on neutral
@@ -390,7 +396,8 @@ def doctor_config(args: argparse.Namespace) -> int:
390396
for key in sorted(prov):
391397
entry = prov[key]
392398
value = entry['value']
393-
if _is_sensitive_key(key) and value not in (None, ''):
399+
redact = _is_sensitive_key(key) or key in _credential_endpoint_keys
400+
if redact and value not in (None, ''):
394401
value = _REDACTED
395402
config.append({'key': key, 'value': value, 'source': entry['source']})
396403

teaagent/ergonomics/workspace_defaults.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,17 @@ def _env_source(env_name: str) -> str:
200200

201201
for key, (env_name, coerce) in _ENV_ONLY.items():
202202
raw = os.environ.get(env_name)
203-
if raw:
204-
prov[key] = {'value': coerce(raw), 'source': _env_source(env_name)}
203+
if not raw:
204+
continue
205+
try:
206+
value = coerce(raw)
207+
except (ValueError, TypeError):
208+
# Malformed env value (e.g. TEAAGENT_DAILY_COST_CAP_CENTS=abc):
209+
# ignore the override and fall back to the prior layer rather than
210+
# crashing every command. The retained source (default/config) makes
211+
# it visible in `doctor config` that the env var did not take effect.
212+
continue
213+
prov[key] = {'value': value, 'source': _env_source(env_name)}
205214

206215
return prov
207216

tests/test_workspace_defaults_toml.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,50 @@ def test_resolve_config_provenance_distinguishes_env_file_from_shell(
302302
}
303303

304304

305+
def test_resolve_config_provenance_ignores_malformed_numeric_env(
306+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
307+
) -> None:
308+
"""A malformed numeric env var is ignored (falls back), not a crash (F-D)."""
309+
from teaagent.ergonomics.workspace_defaults import (
310+
load_workspace_defaults,
311+
resolve_config_provenance,
312+
)
313+
314+
monkeypatch.setenv('TEAAGENT_DAILY_COST_CAP_CENTS', 'not-a-number')
315+
316+
prov = resolve_config_provenance(tmp_path)
317+
assert prov['daily_cost_cap_cents']['value'] == 0 # default, override ignored
318+
assert prov['daily_cost_cap_cents']['source'] == 'default'
319+
# load_workspace_defaults (derived) must also not crash.
320+
assert load_workspace_defaults(tmp_path)['daily_cost_cap_cents'] == 0
321+
322+
323+
def test_doctor_config_redacts_webhook_url(
324+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
325+
) -> None:
326+
"""The webhook URL value is redacted (may embed a token) but its source is
327+
still shown (F-C)."""
328+
import argparse
329+
import json
330+
331+
from teaagent.cli._handlers._doctor import doctor_config
332+
333+
monkeypatch.setenv(
334+
'TEAAGENT_AUTOMATION_WEBHOOK_URL', 'https://example.com/hook?token=abc123secret'
335+
)
336+
rc = doctor_config(argparse.Namespace(root=str(tmp_path)))
337+
assert rc == 0
338+
339+
cfg = {e['key']: e for e in json.loads(capsys.readouterr().out)['config']}
340+
assert 'abc123secret' not in json.dumps(cfg)
341+
assert cfg['automation_webhook_url']['value'] != (
342+
'https://example.com/hook?token=abc123secret'
343+
)
344+
assert cfg['automation_webhook_url']['source'] == (
345+
'env:TEAAGENT_AUTOMATION_WEBHOOK_URL'
346+
)
347+
348+
305349
def test_load_workspace_defaults_matches_provenance_values(
306350
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
307351
) -> None:

0 commit comments

Comments
 (0)