Skip to content

fix(sessions): reject session IDs with a trailing newline - #1187

Open
shoemoney wants to merge 1 commit into
anthropics:mainfrom
shoemoney:fix/validate-uuid-trailing-newline
Open

fix(sessions): reject session IDs with a trailing newline#1187
shoemoney wants to merge 1 commit into
anthropics:mainfrom
shoemoney:fix/validate-uuid-trailing-newline

Conversation

@shoemoney

Copy link
Copy Markdown

The bug

_validate_uuid documents a precise contract (src/claude_agent_sdk/_internal/sessions.py:70):

Returns the string if it is a valid UUID, else None.

It accepts one string that is not a valid UUID. Python's $ also matches immediately before a trailing newline, so re.match(r"^...$", s) succeeds on "<uuid>\n" — and the newline is preserved in the returned value:

>>> from claude_agent_sdk._internal.sessions import _validate_uuid
>>> _validate_uuid("3f2504e0-4f89-11d3-9a0c-0305e82c3301")
'3f2504e0-4f89-11d3-9a0c-0305e82c3301'
>>> _validate_uuid("3f2504e0-4f89-11d3-9a0c-0305e82c3301\n")
'3f2504e0-4f89-11d3-9a0c-0305e82c3301\n'

\r\n, leading whitespace and trailing junk are all correctly rejected — it is specifically the single trailing \n that slips through, which is the one a caller is most likely to actually have.

Why it matters

There are nine call sites. The validated value is used to build a filename (sessions.py:781):

file_name = f"{uuid}.jsonl"

3f2504e0-...-0305e82c3301\n.jsonl never exists, so the failure is silent and typed as "not found":

entry point result with a trailing newline
get_session_info None
get_session_messages []
list_subagents []

A caller cannot distinguish "you passed a malformed session ID" from "that session does not exist" — which is the distinction the validator exists to make. Acquiring the stray byte is ordinary: reading an ID out of a file, or capturing it from command output.

The fix

fullmatch anchors both ends with no newline exemption, so the redundant ^/$ come out with it. Two lines.

Verification

Against the real imported function, before and after:

--- before (main) ---
  clean       -> '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
  trailing \n -> '3f2504e0-4f89-11d3-9a0c-0305e82c3301\n'    <- accepted

--- after ---
  clean       -> '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
  trailing \n -> None                                          <- rejected

Full suite and lint:

$ uv run --extra dev pytest -q
1337 passed, 5 skipped in 11.15s

$ uv run --extra dev ruff check src/
All checks passed!

$ uv run --extra dev ruff format --check src/claude_agent_sdk/_internal/sessions.py
1 file already formatted

Not included

I didn't add a regression test, to keep this to the two-line change — but tests/test_sessions.py is the obvious home for one and it's a three-line assert. Happy to push it here if you'd rather have the behaviour pinned.

I also left the other three compiled patterns in this file alone. _SKIP_FIRST_PROMPT_PATTERN.match is a deliberate prefix match, and the other two are used with findall/sub, so none of them have this issue.

`_validate_uuid` documents itself as "Returns the string if it is a valid
UUID, else None", but `re.match` with a `$` anchor accepts one more string
than that: Python's `$` also matches immediately before a trailing newline.

    _validate_uuid("3f2504e0-4f89-11d3-9a0c-0305e82c3301\n")
    -> '3f2504e0-4f89-11d3-9a0c-0305e82c3301\n'

The newline is preserved in the returned value and flows into the nine call
sites, where it becomes part of a filename:

    file_name = f"{uuid}.jsonl"      # sessions.py:781

That path never exists, so `get_session_info` returns None and
`get_session_messages` / `list_subagents` return [] -- indistinguishable
from a session that genuinely is not there. A session ID read from a file
or captured from command output is the common way to acquire the trailing
byte.

Switch to `fullmatch`, which anchors both ends with no newline exemption,
and drop the now-redundant `^`/`$`.
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