fix(sessions): pick the last JSON field match by position, not per pattern - #1208
Open
chintan-diwakar wants to merge 1 commit into
Open
fix(sessions): pick the last JSON field match by position, not per pattern#1208chintan-diwakar wants to merge 1 commit into
chintan-diwakar wants to merge 1 commit into
Conversation
Author
|
Public-API reproduction. Uses only import json, os, tempfile
from pathlib import Path
tmp = tempfile.mkdtemp(prefix="sdk-repro-")
config_dir = Path(tmp) / "claude-config"
project = Path(tmp) / "myproject"
project.mkdir()
os.environ["CLAUDE_CONFIG_DIR"] = str(config_dir)
from claude_agent_sdk import ( # env var must be set first
get_session_info, list_sessions, project_key_for_directory, rename_session,
)
sid = "550e8400-e29b-41d4-a716-446655440000"
# A host tool writes the transcript with plain json.dumps -> SPACED form.
entries = [
{"type": "user", "uuid": "u1", "sessionId": sid,
"timestamp": "2026-08-01T10:00:00Z", "cwd": str(project),
"message": {"role": "user", "content": "help me refactor the parser"}},
{"type": "custom-title", "customTitle": "Imported Title", "sessionId": sid},
]
f = config_dir / "projects" / project_key_for_directory(project) / f"{sid}.jsonl"
f.parent.mkdir(parents=True)
f.write_text("".join(json.dumps(e) + "\n" for e in entries))
# The user renames through the SDK. This appends a COMPACT line at EOF.
rename_session(sid, "Renamed Title", directory=str(project))
print("last line of file:", f.read_text().rstrip().rsplit("\n", 1)[-1])
info = get_session_info(sid, directory=str(project))
print("get_session_info custom_title:", info.custom_title)
print("list_sessions[0] summary: ",
list_sessions(directory=str(project), include_worktrees=False)[0].summary)On With this PR: |
…ttern
_extract_last_json_string_field scans the two accepted serializations one
after the other ('"key":"' then '"key": "'). The second pattern's last match
overwrote the first's unconditionally, so in a transcript that contains both
spacings an EARLIER spaced-form value beat a LATER compact-form one, which
contradicts the function's documented "finds the LAST occurrence" contract.
User-visible effect: rename_session() and tag_session() append compact JSON,
but a transcript written by a host tool with a bare json.dumps carries the
spaced form. When both are present the rename is silently ignored and
get_session_info()/list_sessions() keep reporting the stale title forever.
lastPrompt, summary and gitBranch go stale the same way.
Track the winning match's index and only overwrite when the new match starts
later in the text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chintan-diwakar
force-pushed
the
fix/last-json-field-position
branch
from
August 15, 2026 08:46
f3cee4f to
e365f7c
Compare
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.
Summary
_extract_last_json_string_field(_internal/sessions.py:229) scans the two accepted serializations one after the other,"key":"then"key": ", and lets the second pattern's last match overwrite the first's unconditionally. So it returns the last match per pattern, not the last match by position, contradicting its own docstring ("finds the LAST occurrence").When a transcript holds both spacings, an earlier
"key": "value"beats a later"key":"value". The user-visible result is thatrename_session()is silently ignored: the new title is physically the last line of the file, andget_session_info()/list_sessions()keep returning the old one permanently.Both spacings occur in practice.
rename_session()appends compact JSON (session_mutations.py:98usesseparators=(",", ":")), while anything that serialized the transcript with a barejson.dumps(entry)writes the spaced form, since Python's defaults are", "/": ". This repo's own fixtures do exactly that attests/test_sessions.py:88._parse_session_info_from_literoutescustomTitle,aiTitle,lastPrompt,summary,gitBranchandtagthrough this helper, sotag_session()and the last-prompt, summary and branch fields go stale the same way.Fix
src/claude_agent_sdk/_internal/sessions.py: track the winning match's start index and only overwrite when a later one is found. Four lines. Scanning, escape handling and the truncated-line break are unchanged, so files using a single spacing behave exactly as before.tests/test_sessions.py:test_extract_last_json_string_field_mixed_spacingcovers both orderings at the unit level.tests/test_session_mutations.py:test_rename_wins_over_earlier_spaced_titleis the end-to-end regression, renaming over a pre-existing spaced title and assertinglist_sessions()reports the rename.Tests
mainand pass with the fix (2 failed, 152 passedwithsessions.pyreverted).python -m pytest tests/: 1368 passed, 5 skipped, same baseline asmain.python -m ruff check src/ tests/,ruff format --checkandpython -m mypy src/: clean.A public-API reproduction, using only
rename_session,get_session_info,list_sessionsandproject_key_for_directory, is in the first comment below.Note on the sibling helper
_extract_json_string_field(sessions.py:205) has the mirrored flaw: it documents "returns the first match" but tries the compact pattern to completion first, so a later compact match can beat an earlier spaced one. It feedscreated_at,cwdand thegitBranchhead fallback. I left it out to keep this to one defect with one demonstrated symptom, and am happy to fold in the same-shape fix if you would prefer both together.Used AI assistance; reviewed and tested by me.