refactor: surface error causes across config, storage, and keychain (desloppify C10)
Stops five read paths from silently collapsing failure modes into null.
Each path now distinguishes 'file absent' (returns null, silent) from
'file exists but broken' (logs root cause, returns null or throws typed
error). Callers can act on the distinction instead of guessing.
src/fingerprint/schema.ts
- Add ManifestValidationError (extends Error) so callers can discriminate
via instanceof without string-matching.
- Replace all 16 bare 'throw new Error(...)' sites in
validateCandidateManifest and validateVerifiedManifest with
ManifestValidationError.
src/fingerprint/loader.ts
- readJsonFile now throws ManifestValidationError for malformed JSON (with
cause chain) and keeps returning null only for ENOENT.
- loadCandidateManifest, loadVerifiedManifest, and loadManifestIndex catch
ManifestValidationError, log via new logManifestValidationFailure (tier,
subject, root cause), negative-cache, and return null. Unrelated throws
propagate.
src/storage.ts
- loadAccounts: split the catch-all into a read step and a parse step. Both
return null, but non-ENOENT read errors and JSON parse errors now go
through logStorageReadFailure(path, error) with errno code before the null
return, so operators notice EACCES/EISDIR/corrupt-storage instead of a
silent empty-accounts startup.
src/config.ts
- Add ConfigCorruptReadError (extends Error, message carries the refused
path). saveConfig now reads the existing file via the new readRawConfig
helper; if readOK=false (file present but unparseable), it throws
ConfigCorruptReadError instead of merging updates onto {} and clobbering
the corrupt file with a partial write.
- loadConfig and loadRawConfig route through readRawConfig and call
logConfigReadFailure on non-ENOENT failures with errno code + message.
src/cc-credentials.ts
- runSecurityCommand now tags each failure path (handled exit code,
ETIMEDOUT/SIGTERM timeout, unexpected error with status/code/signal
detail) and calls logSecurityCommandFailure gated on
OPENCODE_ANTHROPIC_DEBUG=1. Command argv is redacted to the first
token via redactCommand so the -w keychain-read flag and service names
never leak to logs. Behavior stays null-on-failure; only observability
changes.
Tests
- schema.test.ts: 2 new instanceof assertions (+1 regression lock that
error.name === 'ManifestValidationError').
- loader.test.ts: 1 new test proving ENOENT returns null without throwing.
- config.test.ts: 5 new tests pinning saveConfig corrupt-refuse contract
(invalid JSON, array, fresh install, happy path, error message carries
the refused path).
- cc-credentials.test.ts: 4 new tests covering DEBUG=1 log content for
handled-exit/timeout, silent-default, and argv redaction (no
'Claude Code-credentials' or '-w' in log output).
Scoped suites green: 41 fingerprint, 62 config, 34 storage, 20 cc-credentials.
Resolves desloppify cluster error-surface-improvements (5/5 members, 3/3
steps). No runtime behavior change on the success path; only error paths
gain diagnostics. V
Vacbo committed
625d18e37eb50856c01ebd4edc489b86aa244def
Parent: 844c7cc