fix(oauth): degrade to re-auth on corrupt credential cache instead of crashing#208
Conversation
… crashing DirectoryPersistence.readTokens/readClientInfo/readState routed a corrupt (truncated or malformed) cache file's JSON.parse SyntaxError straight up through CompositePersistence, crashing the MCP connection instead of degrading to re-auth. Every sibling reader already tolerates this: VaultPersistence (oauth-vault.ts) catches SyntaxError to rebuild, and the daemon/server-proxy readers wrap in catch-all. These three were the lone outliers. Wrap the three JSON readers in a narrow helper that maps only SyntaxError to undefined; genuine I/O faults still propagate. readJsonFile is left untouched so the vault keeps distinguishing corrupt-from-missing for its repair path. Fixes openclaw#207.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 175e5b10f1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codex review on openclaw#208 noted that making readState() corrupt-tolerant skips the CSRF state check on the authorization callback: oauth.ts only rejects when the stored expectedState is truthy, so a corrupt state.txt degrading to undefined would let a mismatched/absent callback state through. Narrow the tolerance to the credential caches only (readTokens/readClientInfo). readState() keeps throwing on corrupt input so the OAuth flow fails closed. Test now asserts state reads reject with SyntaxError while tokens/client degrade.
|
Good catch, thanks — addressed in 94e66b1. You're right that |
|
ClawSweeper review: did not complete due to Codex infrastructure failure. Reviewed June 16, 2026, 9:04 AM ET / 13:04 UTC. Summary Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path. Review metrics: none identified. Merge readiness This is a ClawSweeper/Codex infrastructure failure, not a PR readiness or patch-quality verdict. Risk before merge
Maintainer options:
Next step before merge
Review detailsBest possible solution: Retry the Codex review after fixing the execution failure. Do we have a high-confidence way to reproduce the issue? Unclear. The review failed before ClawSweeper could establish a reproduction path. Is this the best way to solve the issue? Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction. AGENTS.md: unclear because the file could not be read completely. Codex review notes: model internal, reasoning high; reviewed against 37391ce70b3f. Evidence reviewedWhat I checked:
Likely related people:
How this review workflow works
|
|
Maintainer verification for bea0ed6:
@codex review |
Problem
When a cached OAuth credential file under an explicit
tokenCacheDir(or a legacy per-server cache during migration) is present but corrupt — truncated or malformed JSON, e.g. from an interrupted write —DirectoryPersistence's readers let theJSON.parseSyntaxErrorpropagate. ThroughCompositePersistencethat crashes the MCP connection instead of treating the file as "no usable credentials" and degrading to re-auth.Root cause
DirectoryPersistence.readTokens()andreadClientInfo()callreadJsonFile, which maps onlyENOENTtoundefinedand re-throws everything else (includingSyntaxError).This is asymmetric with the rest of the codebase, which already treats a corrupt credential file as recoverable:
VaultPersistence/readVaultState(oauth-vault.ts) catchesSyntaxErrorand rebuilds (needsRepair)daemon/host.tsandserver-proxy.tswrap their reads in catch-all handlersThese
DirectoryPersistencereaders were the lone exceptions.Fix
Route the credential readers (
readTokens,readClientInfo) through a small private helper (readJsonOrUndefined) that maps onlySyntaxError→undefined; genuine I/O faults still propagate. Mirrors the existingVaultPersistencepattern (if (!(error instanceof SyntaxError)) throw error).OAuth
stateis deliberately excluded. A corruptstate.txtmust fail the flow closed:oauth.tsonly enforces the callback state check whenexpectedStateis truthy, so degrading state toundefinedwould skip the CSRF check (thanks @chatgpt-codex-connector for catching this on the first pass).readState()therefore keeps throwing on corrupt input.readJsonFileitself is also left untouched, since the vault relies on it throwingSyntaxErrorto distinguish corrupt-from-missing for its repair path.Verification
Run locally (Node 22, vitest):
tests/oauth-persistence.test.tswrites corrupttokens.json/client.json/state.txtand assertsreadTokens()/readClientInfo()resolve toundefinedwhilereadState()rejects withSyntaxError(CSRF fail-closed).main(SyntaxError: Unterminated string in JSON); they pass with the fix.oauth-persistencesuite: 35/35 passing.tsgo --noEmit,oxfmt --check,oxlint --type-aware: clean.Fixes #207.