Skip to content

feat: add tag/created_at to SDKSessionInfo + get_session_info#667

Merged
qing-ant merged 11 commits intomainfrom
qing/sdk-session-info-expand-py
Mar 20, 2026
Merged

feat: add tag/created_at to SDKSessionInfo + get_session_info#667
qing-ant merged 11 commits intomainfrom
qing/sdk-session-info-expand-py

Conversation

@qing-ant
Copy link
Contributor

@qing-ant qing-ant commented Mar 10, 2026

Mirrors TypeScript PR claude-cli-internal#21659.

Changes

  • SDKSessionInfo dataclass += tag: str | None, created_at: float | None
  • list_sessions() now extracts tag from session tail (last occurrence wins; empty string → cleared)
  • NEW: get_session_info(session_id, directory=None) -> SDKSessionInfo | None — single-session metadata lookup without O(n) directory scan
  • Extracted _parse_session_info_from_lite() shared helper (matches TS parseSessionInfoFromLite)
  • SDKSessionInfo.file_size is now int | None (was int) — forward-compat for pluggable storage backends where file size isn't meaningful. Local JSONL storage still populates it.

Port notes

  • get_session_info includes worktree fallback per TS fix 2b2d7b91f4, matching get_session_messages semantics.
  • Empty-string tag resolves to None (cleared) via existing or None pattern.
  • created_at parsed from first entry's ISO timestamp field → epoch ms. Python 3.10's datetime.fromisoformat() doesn't support trailing Z, so we replace Z+00:00 before parsing. More reliable than stat().birthtime which is unsupported on some filesystems.
  • Add get_session_info() for single-session metadata lookup
  • Add tag and created_at fields to SDKSessionInfo; make file_size optional

Tests

20 new tests (TestTagExtraction, TestCreatedAtExtraction, TestGetSessionInfo). 286 total pass. Ruff + mypy clean.

…n_info

Ports TS SDK changes from qing/sdk-session-info-expand (PR #21659).

- Add tag and agent_name fields to SDKSessionInfo dataclass
- Refactor inline field extraction into _parse_session_info_from_lite
  helper for reuse between list_sessions and get_session_info
- Extract tag from tail via last-occurrence scan (empty string = cleared)
- Extract agent_name scoped to {type:'agent-name'} entries only; bare
  tail-scan would pick up per-message agentName from swarm sessions
  (handles both compact and spaced JSON variants)
- Add get_session_info(session_id, directory=None) for single-session
  metadata lookup without O(n) directory scan; includes worktree fallback
  when directory is provided (matches get_session_messages semantics)
- Export get_session_info from package root

Tests: 75 passed. Ruff + mypy clean.
Extracts creation timestamp from first entry's ISO timestamp field in
the head buffer. Returns epoch ms for consistency with last_modified.
More reliable than stat().birthtime which is unsupported on some
filesystems.

Python 3.10 compatibility: datetime.fromisoformat() in 3.10 doesn't
support trailing 'Z', so we replace it with '+00:00' before parsing.

Mirrors claude-cli-internal commit 2470efe469.

Tests: 288 passed. Ruff + mypy clean.
agent_name is tied to feature-gated swarm mode — not a general SDK
concept. Removing to keep SDKSessionInfo focused on universal metadata.

Mirrors TS change in claude-cli-internal#21659.

Tests: 286 passed. Ruff + mypy clean.
file_size is a local-JSONL concept with no remote-storage equivalent.
Making optional now for forward-compat with pluggable storage backends.

Local _parse_session_info_from_lite still populates it from fstat size;
this is a type loosening only. Existing tests pass unchanged.

Mirrors claude-cli-internal PR #21659.
…-expand-py

# Conflicts:
#	src/claude_agent_sdk/__init__.py
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — clean TS port with comprehensive tests; the file_size optionality change and new get_session_info API follow established patterns.

Extended reasoning...

Overview

This PR adds two new fields (tag, created_at) to the SDKSessionInfo dataclass, makes file_size optional (int | None), introduces a new public get_session_info() function for single-session lookup, and extracts a shared _parse_session_info_from_lite() helper. Four files are modified: the types module, sessions implementation, public __init__.py exports, and tests.

Security risks

None. All changes involve read-only local file operations (reading JSONL session files from the user's own config directory). No auth, network, or injection surfaces are affected.

Level of scrutiny

Medium. While this adds a new public API function and modifies a public dataclass, the changes are a direct port from an already-reviewed TypeScript PR. The new get_session_info() function mirrors the existing get_session_messages() pattern almost exactly (same directory resolution, worktree fallback, UUID validation). The _parse_session_info_from_lite() extraction is a clean refactor — the old inline code in _read_sessions_from_dir is replaced by a call to the new helper with no behavioral changes. No CODEOWNERS file exists.

Other factors

  • Test coverage: 20 new tests across three test classes (TestTagExtraction, TestCreatedAtExtraction, TestGetSessionInfo) covering edge cases like empty tags, invalid timestamps, sidechain filtering, worktree fallback, and cross-project search.
  • Breaking change: file_size: intfile_size: int | None = None is technically a type-level breaking change, but it's backward-compatible at runtime since it has a default value, and existing local JSONL storage still populates it.
  • Python 3.10 compat: The Z+00:00 replacement for datetime.fromisoformat() is a known workaround and is correctly applied.

Four fixes from a 43-agent adversarial review against the TS reference
(claude-cli-internal src/utils/listSessionsImpl.ts @ main):

1. Tag extraction scoped to {"type":"tag"} lines (sessions.py:455)

   Bare _extract_last_json_string_field(tail, "tag") matches ANY "tag":"..."
   in the 64KB tail — including tool_use inputs from Docker builds, git tag
   via MCP, cloud resource tagging. Proven: test_tag_none_when_only_tool_use_tag
   returned tag='prod' from tool input before the fix.

   TS scopes to lines starting with '{"type":"tag"' at column 0
   (listSessionsImpl.ts:132, sessionStorage.ts:629 with the canonical
   comment about this exact collision class).

   Tests now use separators=(",", ":") to match real on-disk format
   (CLI jsonStringify, SDK tag_session, TS sessionMutationsImpl all write
   compact JSON). 2 regression tests added.

2. custom_title chain: + head scan, + aiTitle fallback (sessions.py:426)

   TS chain is tail.customTitle || head.customTitle || tail.aiTitle ||
   head.aiTitle (listSessionsImpl.ts:97-102). Pre-existing drift from TS
   PRs #21333 (aiTitle) and #20390 landing after Python #622 — fixing here
   since this PR touches the exact block.

3. summary chain: + lastPrompt fallback (sessions.py:436)

   TS inserts tail.lastPrompt between custom_title and summary
   (listSessionsImpl.ts:115-119). Same drift class as (2).

4. created_at: float -> int (types.py:998, sessions.py:465)

   last_modified is int; both are epoch-ms. Wrapped
   datetime.timestamp() * 1000 in int().
@qing-ant qing-ant changed the title feat: add tag/agent_name to SDKSessionInfo + get_session_info feat: add tag/created_at to SDKSessionInfo + get_session_info Mar 18, 2026
@qing-ant
Copy link
Contributor Author

Pushed a112d60 — 4 TS-parity fixes from adversarial review against claude-cli-internal/src/utils/listSessionsImpl.ts:

Before After
Tag scoping _extract_last_json_string_field(tail, "tag") — matched tool_use inputs (Docker --tag, git tag MCP, cloud resource tags). Proven: tag='prod' leaked from {"input":{"tag":"prod"}} Scoped to '{"type":"tag"' at col 0, mirrors listSessionsImpl.ts:132 / sessionStorage.ts:629. +2 regression tests.
custom_title chain tail.customTitle only tail.customTitle ∥ head.customTitle ∥ tail.aiTitle ∥ head.aiTitle — catches up to TS #21333
summary chain missing lastPrompt customTitle ∥ tail.lastPrompt ∥ tail.summary ∥ firstPrompt — catches up to TS #20390
created_at type float | None int | None — matches last_modified: int

Also retitled (dropped agent_name — not in the diff).

ruff ✓ · mypy ✓ · 81/81 pass

- Add is_dir() filter to get_session_info's projects_dir scan,
  matching _list_all_sessions/_list_sessions_for_project
- Fix float literal in test_created_at_without_z_suffix (created_at
  is int | None)
Change _extract_json_string_field(head, "timestamp") to use first_line
instead of the entire 64KB head buffer, avoiding false matches from
later entries.
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All previous nits addressed; clean TS port with good test coverage.

Extended reasoning...

Overview

This PR adds tag and created_at fields to SDKSessionInfo, introduces get_session_info() for single-session metadata lookup, extracts _parse_session_info_from_lite() as a shared helper, and makes file_size optional. It touches 4 files: __init__.py (exports), sessions.py (core logic), types.py (dataclass), and test_sessions.py (20+ new tests).

Security risks

None. The code reads local JSONL session files and extracts metadata fields. No auth, crypto, network, or user-input-to-execution paths are involved.

Level of scrutiny

This is a TS port with clear scope. The design decisions (field naming, fallback chains, tag scoping) were already vetted in the TypeScript codebase. All four nits from my previous two review rounds have been addressed: is_dir() filter added, timestamp scoped to first_line, custom_title docstring updated, and test assertion fixed from float to int literal.

Other factors

  • No bugs found by the bug hunting system.
  • The file_size type change (intint | None) is technically breaking but well-motivated (forward-compat for remote backends) and backwards-compatible in practice (defaults to None).
  • New get_session_info() follows the exact pattern of existing get_session_messages() including worktree fallback.
  • Comprehensive test coverage: tag extraction (7 tests including tool_use isolation), created_at (6 tests including edge cases), and get_session_info (9 tests).
  • The author performed an adversarial review against the TS implementation and fixed 4 TS-parity gaps before my reviews.

@qing-ant qing-ant enabled auto-merge (squash) March 20, 2026 17:55
@qing-ant qing-ant merged commit f144dcc into main Mar 20, 2026
10 checks passed
@qing-ant qing-ant deleted the qing/sdk-session-info-expand-py branch March 20, 2026 17:58
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.

2 participants