Skip to content

fix(config): reject invalid cache_ttl instead of silently defaulting - #67

Merged
aaearon merged 9 commits into
mainfrom
fix/cache-config-semantics
Aug 16, 2026
Merged

fix(config): reject invalid cache_ttl instead of silently defaulting#67
aaearon merged 9 commits into
mainfrom
fix/cache-config-semantics

Conversation

@aaearon

@aaearon aaearon commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Part 6 of 8. Base: test/isolation-harness (#63) — must merge first.

⚠️ User-visible behaviour change

ParseCacheTTL returns (time.Duration, error) and now rejects any explicitly-supplied invalid value:

config before after
absent 4h default 4h default (unchanged)
cache_ttl: garbage silent 4h default error
cache_ttl: 0s accidental never-read-but-still-write mode error

The original plan rejected only <= 0. That was an anti-pattern — it would have validated the same field two opposite ways, since "garbage" already defaulted silently. Consistency was the correct fix.

grant configure is deliberately unaffected: it never calls Load, so it remains a working recovery path for rewriting a broken config.

Also

  • grant request submit discarded the Load error and substituted DefaultConfig(), so it was the one command where an invalid TTL was neither rejected nor noticed — and the ParseCacheTTL error handling below it was unreachable dead code. Fixed, and the config load moved ahead of bootstrapSCAService() so the failure surfaces without an auth round-trip (which is also what makes it testable).
  • maxSessionAgesessionTimestampRetention (still 24h). Its old comment claimed entries are "removed on cleanup" — false; CleanupSessions filters on active IDs and never reads it.
  • TestGet_CorruptJSON passed via the TTL branch, not the unmarshal branch. Rewritten so only the unmarshal guard can produce the miss.
  • Portable Windows coverage for Load's read-error branch (the existing test skips on Windows, leaving that leg uncovered).
  • The error now names the offending file.

A trap worth recording

The planned MkdirAll assertion did not kill its mutation: with the error swallowed, the subsequent WriteFile fails for the same ENOTDIR reason, so Save still errored and a naive check passed. Tightened to assert the fs.PathError Op is mkdir — set by platform-independent Go code, so it is portable.

Also corrected: the claim that this construction yields ERROR_DIRECTORY on Windows. It does not — os.MkdirAll synthesises ENOTDIR itself on both platforms.

Known limit

The seven buildCachedLister call sites' error propagation is unpinned — they sit in production RunE closures unreachable from unit tests. Static reading confirms all are correct; the ledger says so rather than reading a bare done.

22 mutations reverified. Adversarial review performed (Codex credits exhausted; review by a Claude agent). Both blocking findings fixed.

@aaearon aaearon closed this Aug 15, 2026
@aaearon aaearon reopened this Aug 15, 2026
@aaearon
aaearon deleted the branch main August 16, 2026 07:34
@aaearon aaearon closed this Aug 16, 2026
@aaearon aaearon reopened this Aug 16, 2026
@aaearon
aaearon changed the base branch from test/isolation-harness to main August 16, 2026 07:37
@aaearon
aaearon force-pushed the fix/cache-config-semantics branch from dd10a0d to 49c1ff8 Compare August 16, 2026 07:40
…l guard

Renames maxSessionAge to sessionTimestampRetention and corrects its comment:
CleanupSessions filters on activeIDs membership and never reads the constant.
ParseCacheTTL now returns (time.Duration, error). Absent still means the 4h
default; unparseable, zero and negative values are all rejected, consistently.
Validation runs in config.Load so a bad value surfaces at startup rather than
when a command happens to build a cache. buildCachedLister and its seven
command call sites propagate the error.

Adds config coverage for partial-YAML defaults, non-nil Favorites, invalid
YAML, ConfigDir naming, Save's 0600/0700 modes, MkdirAll failure, and a
portable read-error test that also runs on the Windows CI leg.
Both resolution steps discarded the config.LoadDefaultWithPath error and
substituted DefaultConfig(). Since Load started rejecting an invalid
cache_ttl, that made `request submit` the one command where a bad value
was neither honored nor reported.

Propagate the error, and load the config before authenticating so the
failure does not require a working auth cycle first. Extract the
on-demand cache construction into buildCachedRolesLister, mirroring
buildCachedLister, so its bad-TTL arm is reachable from a test.

Also pin that `grant configure` still works over a broken config: it
never calls Load, which is what keeps it a recovery path.
"invalid cache_ttl" alone leaves a user with a non-default GRANT_CONFIG
no indication of which file to edit.

Also tighten TestLoad_InvalidYAMLErrors to assert the yaml parse text:
Load now has a second error source (the cache_ttl validation) that a
bare "did it error" check would accept.
The changelog entry was 203 chars against the documented ~120, carried
mechanism notes that belong in the PR body, and said "at startup" —
which is wrong in both directions: nothing fails before a command
reaches config.Load, and `configure` never reaches it at all.

The "ERROR_DIRECTORY on Windows" claim was also wrong. os.MkdirAll
(os/path.go) stats the parent itself and synthesizes
&PathError{Op: "mkdir", Err: syscall.ENOTDIR} in platform-independent
Go, so it is ENOTDIR on both platforms. Corrected in CLAUDE.md, the
test comment and the ledger.

Record in the ledger that the seven buildCachedLister call sites'
error propagation remains unpinned, and why it is not worth
restructuring production code to fix.
Name the expected duration syntax on the unparseable arm (still wrapping
the time.ParseDuration error) and name --refresh on the non-positive arm,
so someone who used 0s as a cache kill-switch has a replacement.
runConfigure rebuilds the config from scratch, dropping favorites and
default_provider. Rename the test, pin that loss, and record the sharp
edge in CLAUDE.md; the remedy is to edit the file the error names.
request submit bootstraps its service in RunE first, so an
unauthenticated user hits the auth prompt before the config error.
@aaearon
aaearon force-pushed the fix/cache-config-semantics branch from 49c1ff8 to e9889e5 Compare August 16, 2026 13:29
@aaearon
aaearon merged commit 7fe8a68 into main Aug 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant