Repository navigation
feat: add persist_session option - #1476
model-bender wants to merge 1 commit into
Conversation
The TypeScript SDK's persistSession option has no Python counterpart, so turning off transcript writes meant reaching for extra_args. With persist_session=False the CLI runs with --no-session-persistence and saves nothing under ~/.claude/projects/. As in the TypeScript SDK, the flag is sent only for an explicit False, and session_store is rejected alongside it because the store mirrors the transcript from those local writes. The e2e tests follow the verbatim_prompts layout: a control run that list_sessions() finds, then query() and ClaudeSDKClient runs with persist_session=False that it doesn't.
| ``resume`` unless ``fork_session`` is also set. | ||
| """ | ||
|
|
||
| persist_session: bool = True |
There was a problem hiding this comment.
ClaudeAgentOptions is a positional dataclass, so inserting persist_session before max_turns changes the meaning of existing positional constructor calls. A caller that previously supplied max_turns=3 by position now sets persist_session=3 and shifts every following argument, silently changing session behavior. Could this field be appended to the dataclass or made keyword-only to preserve constructor compatibility? This is the same ABI concern as the new field in #1477.
There was a problem hiding this comment.
Thanks for the comment. Same reasoning as on #1477 (#1477 (comment)): I put persist_session beside resume and session_id because new options here go next to related ones rather than at the end, and every ClaudeAgentOptions(...) call in the repo and docs uses keyword arguments. I'll move both fields to the end if the maintainers prefer.
There was a problem hiding this comment.
Thanks for checking the in-repo usage, @model-bender. My concern is external callers of the public positional ClaudeAgentOptions dataclass, which won't necessarily follow the repository's keyword-only examples. Using field(default=True, kw_only=True) for persist_session would let it stay next to the related session options without shifting existing positional arguments such as max_turns. Could we consider that and add a compatibility test? Happy to defer to the maintainers' preferred public API convention.
There was a problem hiding this comment.
Thanks, that's a fair point about external callers. The maintainers' own option PRs add fields mid-class without kw_only, for example strict_mcp_config at position 5 of 44 (#915) and verbatim_prompts at 32 of 49 (#1269), so the positional order already changes between releases. I'd rather follow that convention than add the first kw_only field in types.py, but I'm happy to switch, with the test, if a maintainer prefers it.
|
Thanks for checking the existing SDK convention, @model-bender. The examples in #915 and #1269 clarify why you placed the option alongside related fields. I wanted the external positional-caller compatibility concern recorded, but I agree the maintainers should decide whether to keep that convention or introduce a keyword-only field. No need to hold up the feature on my comment. Appreciate your thoughtful follow-up. |
|
No worries, and thanks @sylvesterkaczmarek, I appreciate you taking the time to look at this and follow up. Having the positional-caller concern recorded here is useful for the maintainers either way. |
Re-opened from a new fork with the same commits as #1322, which closed when its fork was deleted. Earlier review is on #1322.
Summary
Adds
ClaudeAgentOptions.persist_session, the Python counterpart of the TypeScript SDK'spersistSession. Withpersist_session=Falsethe CLI runs with--no-session-persistenceand writes no transcript under~/.claude/projects/, so ephemeral or automated runs leave no session history behind. The default (True) adds nothing to the command line, so existing behaviour is unchanged.Until now the only way to get this from Python was
extra_args={"no-session-persistence": None}.It follows the TypeScript SDK (checked against
@anthropic-ai/claude-agent-sdk0.3.283):persist_session=False(TS:if (this.options.persistSession === false) args.push("--no-session-persistence")).session_storetogether withpersist_session=Falseis rejected up front, as TS does (sessionStore cannot be used with persistSession: false), because the store mirrors the transcript from those local writes. The check sits invalidate_session_store_options, soquery()andClaudeSDKClientboth fail before the CLI is spawned.Verification
Real model calls. CLI 2.1.283 through
ClaudeSDKClientwithmodel="haiku",setting_sources=[], a fresh tempcwd: two turns (the first reads a file with theReadtool), then aquery(resume=<session_id>).projects/-tmp-persist-real-m5d-fb86/09f8511b-5713-4ef1-92d2-76765b91de18.jsonlis written, and the resume answers from the earlier turnspersist_session=False: both turns succeed, no file anywhere under~/.claudenames the session, and the resume fails withNo conversation found with session ID: a4521e2a-80b8-4019-858a-3192b82a85b8Real CLI, no model calls. The bundled CLI version (2.1.283) driven through
query()with an isolatedCLAUDE_CONFIG_DIR,cwdin a temp dir, andANTHROPIC_BASE_URLpointed at a local server that answers 401. The transcript is written before the first API request, so this shows what persists:projects/-tmp-persist-probe-work-0in8dcqu/0d5dc4b2-ab19-4c6d-b5ba-b491f5e1ad3b.jsonlis writtenpersist_session=False: no.jsonlis writtenThe CLI's
--helpsays this flag "only works with --print". The SDK doesn't pass--print, and the run above shows the flag is honoured in SDK mode anyway.query()andClaudeSDKClientwithsession_store=InMemorySessionStore(), persist_session=Falseboth raise theValueErrorbefore spawning.New tests:
test_build_command_persist_session(tests/test_transport.py), andtest_rejects_persist_session_false_comboandtest_persist_session_false_without_store_is_valid(tests/test_session_store_conformance.py). With thesrc/changes stashed, all three fail (TypeError: ... unexpected keyword argument 'persist_session'); with them, all three pass.New e2e tests in
e2e-tests/test_persist_session.py, laid out like theverbatim_promptsones: a control run thatlist_sessions()finds, thenquery()andClaudeSDKClientruns withpersist_session=Falsethat it doesn't. 3 passed against the real API.ruff check,ruff format --check,mypy src/ scripts/are clean.pytest tests/: 1590 passed, 6 skipped on Python 3.12 and 3.13, and again withmcp==1.23.0(the floor job).