Skip to content

fix(types): cover the values the CLI actually sends in three Literal unions - #1369

Open
v0ropaev wants to merge 1 commit into
anthropics:mainfrom
v0ropaev:fix/literal-unions-match-the-wire
Open

v0ropaev wants to merge 1 commit into
anthropics:mainfrom
v0ropaev:fix/literal-unions-match-the-wire

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Oct 7, 2026

Copy link
Copy Markdown

Three Literal unions in types.py are missing values the CLI puts on the wire. message_parser.py:214 passes these fields through without validating (error=data.get("error")), so the value arrives at runtime while a type checker says it cannot exist.

Checked against the TypeScript SDK published alongside the same CLI, @anthropic-ai/claude-agent-sdk@0.3.292 (CLI 2.1.292):

alias here sdk.d.ts missing
AssistantMessageError 6 13 (:3698, SDKAssistantMessageError) oauth_org_not_allowed, account_on_hold, verification_required, overloaded, model_not_found, max_output_tokens, cloud_credential_error
RateLimitType 5 6 (:5675, the rateLimitType union) seven_day_overage_included
TaskNotificationOriginSubkind 2 4 (:5396, the subkind union) projects-relay, session-inbox

All seven error values are also present as strings in the shipped CLI binary, if you would rather check there than against the TS package.

What it costs a caller. Retrying only on overloaded cannot be written without a cast or an ignore:

if error == "overloaded":          # error: Non-overlapping equality check (left operand type:
    return True                   #   "Literal['authentication_failed', 'billing_error', 'rate_limit',
                                  #   'invalid_request', 'server_error', 'unknown']",
                                  #   right operand type: "Literal['overloaded']")  [comparison-overlap]
                                  # error: Statement is unreachable  [unreachable]

and an exhaustive match over RateLimitType ending in assert_never compiles while silently not handling seven_day_overage_included — the arm for it is reported unreachable. That is the failure mode an exhaustive match exists to prevent. After this change the same file type checks and the assert_never arm becomes genuinely unreachable, which is the point.

Nothing changes at runtime. AssistantMessageError is reordered to follow sdk.d.ts so the two can be diffed by eye when the CLI adds a value next.

Prior art, which I only found while checking for duplicates: #1069 added the RateLimitType value in June with the same reasoning, @aidand-ant approved it, and @sarahdeaton closed it themselves the same day. The value is not on main, so whatever the reason, it did not land. Credit there for that one line.

Two things deliberately left out. PermissionUpdateDestination is missing cliArg on the same grounds, but that is already in my #1330 and belongs there rather than here. And MessageOriginKind carries a docstring saying newer CLI versions may emit kinds not listed and callers should treat anything unrecognized as "not human" — that is a deliberate open union, so I did not touch it. If the three above are meant to be open in the same way, the fix is that docstring rather than these values, and I will do it that way instead.

Tested: test_literal_unions_cover_what_the_cli_sends next to the existing test_effort_level_is_exported, asserting the exact set for each of the three so the next drift shows up as a failing test rather than a silent gap. It fails on main. Full suite 1588 passed, 6 skipped. ruff check, ruff format --check and mypy src/ clean.

Not run: nothing against a live account, since none of this is runtime behaviour. The two numbers that matter are the union contents, which the test pins, and the type-checker output above, which came from running mypy over a caller file before and after.

…unions

message_parser passes these fields through without validating, so a value
missing from the union is one a type checker calls impossible while it
arrives at runtime. Against the TypeScript SDK published alongside the same
CLI, 0.3.292:

- AssistantMessageError had 6 of the 13 in SDKAssistantMessageError
  (sdk.d.ts:3698): oauth_org_not_allowed, account_on_hold,
  verification_required, overloaded, model_not_found, max_output_tokens and
  cloud_credential_error were missing
- RateLimitType had 5 of 6 (sdk.d.ts:5675), missing
  seven_day_overage_included
- TaskNotificationOriginSubkind had 2 of 4 (sdk.d.ts:5396), missing
  projects-relay and session-inbox

What that costs a caller: retrying only on overloaded does not type check,

    error: Non-overlapping equality check (left operand type:
    "Literal['authentication_failed', 'billing_error', 'rate_limit',
    'invalid_request', 'server_error', 'unknown']",
    right operand type: "Literal['overloaded']")
    error: Statement is unreachable

and a match over RateLimitType with assert_never silently skips
seven_day_overage_included rather than failing to compile.

AssistantMessageError is reordered to follow sdk.d.ts so the two can be
diffed by eye. Nothing changes at runtime.

anthropics#1069 added the RateLimitType value in June, was approved, and the author
closed it the same day; the value never landed.

This branch has not been deployed

No deployments
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