Repository navigation
fix(models): skip Claude credentials sources with a non-numeric expiresAt - #5591
Merged
WillemJiang merged 1 commit intoSep 20, 2026
Conversation
…esAt `_extract_claude_code_credential` copied `expiresAt` straight into `ClaudeCodeCredential.expires_at`, so a credentials file whose `expiresAt` is a string, null, list or object reached `is_expired` and raised `TypeError: '<=' not supported between instances of 'str' and 'int'`. That aborted the whole lookup instead of skipping the malformed source and moving on down the documented order, the way the rest of the loader already behaves for a malformed `claudeAiOauth` container. Validate the field the way the sibling branches validate their input: log a debug line and skip the source so the next candidate is tried.
WillemJiang
approved these changes
Sep 20, 2026
1 task
1 of 2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5589
Why
_extract_claude_code_credentialcopiesclaudeAiOauth.expiresAtstraight intoClaudeCodeCredential.expires_atwith no type check —backend/packages/harness/deerflow/models/credential_loader.py:166onmain:ClaudeCodeCredential.is_expired(line 58) then evaluatesif self.expires_at <= 0:. When the credentials file holds"expiresAt": "1773430695128"(a string), ornull, a list, or an object, that comparison raisesout of
load_claude_code_credential(). The exception is not caught, so the lookup loop never advances to the next candidate source — a credentials file at$CLAUDE_CODE_CREDENTIALS_PATHwith a badexpiresAtprevents the loader from ever reaching~/.claude/.credentials.json. It also propagates throughClaudeChatModel.model_post_init, which calls the loader without a guard, so one such file makes everyClaudeChatModelconstruction raise instead of degrading.This is inconsistent with the rest of the function. A missing
expiresAtfalls back to the dataclass default0, whichis_expiredexplicitly tolerates (if self.expires_at <= 0: return False), while an explicitnull— the same "expiry unknown" — aborts the load. The sibling guard for a malformedclaudeAiOauthcontainer (merged in #5494 /bec423188) established the contract for this function: a source that cannot be used must be skipped with a debug log so the loader falls through to the next one.expiresAtwas the last unvalidated input in it.What changed
A credentials source whose
claudeAiOauth.expiresAtis not a number is now skipped with a debug log, in the same shape as the two neighbouring guards in the same function:load_claude_code_credential()no longer raises for such a file: it returns a credential from a later source in the documented lookup order, orNone. Files with a numericexpiresAt, and files with noexpiresAtat all, behave exactly as before — a missing key still yields the dataclass default0and is not treated as expired.Surface area
None of the listed boxes apply — the change is confined to the harness credential loader (
backend/packages/harness/deerflow/models/credential_loader.py), which is not an endpoint underbackend/app, an agent node/graph/prompt change, or a sandbox/skills/dependency change.Bug fix verification
backend/tests/test_credential_loader.py::test_load_claude_code_credential_ignores_non_numeric_expires_at(parametrized over"1773430695128",None,[],{})backend/tests/test_credential_loader.py::test_load_claude_code_credential_falls_back_to_default_when_override_expires_at_is_non_numericmainand green on this branch? yesRed on
main(tests applied, source fix reverted) —cd backend && PYTHONPATH=. PYTHONIOENCODING=utf-8 PYTHONUTF8=1 uv run --locked pytest tests/test_credential_loader.py -k non_numeric -q:Green on this branch, same command:
Validation
AI assistance
Tool(s) used: Claude Code
How you used it: Claude Code drafted the change, the regression tests and the commit message. I ran the red/green cycle by reverting the source fix and re-running the new tests on
main, then on this branch, ran the full backend suite andruffon the branch, and reviewed the final diff before opening this PR.