Skip to content

feat: add list_sessions and get_session_messages top-level functions#622

Merged
qing-ant merged 2 commits into
mainfrom
qing/list-sessions
Mar 3, 2026
Merged

feat: add list_sessions and get_session_messages top-level functions#622
qing-ant merged 2 commits into
mainfrom
qing/list-sessions

Conversation

@qing-ant
Copy link
Copy Markdown
Contributor

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

Adds list_sessions() and get_session_messages() as top-level functions, matching the TypeScript SDK's listSessions() and getSessionMessages().

These are filesystem-reading implementations (not CLI passthroughs) — they scan ~/.claude/projects/ directly, same as the TS SDK.

list_sessions(directory=None, limit=None, include_worktrees=True) -> list[SDKSessionInfo]

  • Scans project directories under ~/.claude/projects/ (or CLAUDE_CONFIG_DIR)
  • Reads head/tail 64KB of each .jsonl file (does not load entire large transcripts)
  • Extracts session_id, summary, last_modified, file_size, custom_title, first_prompt, git_branch, cwd
  • include_worktrees=True expands to active git worktree session dirs; False filters them out
  • Metadata-only sessions are dropped (no '(session)' placeholder)

get_session_messages(session_id, directory=None, limit=None, offset=0) -> list[SessionMessage]

  • Reads one session's .jsonl fully
  • Reconstructs the conversation chain via parentUuid backward traversal (leaf → root, reversed)
  • Handles branched transcripts (sidechains, edit history) by picking the main-line leaf (non-sidechain, non-meta, highest file position)
  • Filters to visible user/assistant messages only
  • Returns [] for nonexistent/invalid session IDs (never raises)
  • Add list_sessions() and get_session_messages() top-level functions
  • Add SDKSessionInfo and SessionMessage dataclasses

Test plan

  • Unit: 59 tests covering path sanitization, JSONL head/tail parsing, parentUuid chain reconstruction, main-chain leaf selection, visibility filtering, limit/offset pagination
  • E2E list_sessions: verified against 637 real sessions — all shape checks pass, include_worktrees expansion confirmed on a 16-worktree repo (270 → 274 sessions), nonexistent dir → clean []
  • E2E get_session_messages: 8 real sessions (~14MB JSONL) — chain reconstruction proven (0 broken links across 1,581 entries), 254-branch session correctly walks main-line, isMeta filtering verified, pagination matches exact slicing

qing-ant added 2 commits March 1, 2026 21:42
Ports listSessions() from the TypeScript SDK as a filesystem-reading
implementation (not a CLI passthrough). Scans ~/.claude/projects/ for
.jsonl session files and extracts metadata via stat + head/tail reads
without full JSONL parsing.

- Add SDKSessionInfo dataclass to types.py (session_id, summary,
  last_modified, file_size, custom_title, first_prompt, git_branch, cwd)
- Add _internal/sessions.py with path sanitization, JSON field
  extraction, first-prompt parsing, and git worktree detection
- Export list_sessions() and SDKSessionInfo from __init__.py
- Filter sidechain sessions and empty metadata-only sessions
  (no '(session)' placeholder fallback)
- Support include_worktrees option (default True) to scan all
  git worktree paths for a project
- 38 tests covering helpers and integration scenarios
Ports getSessionMessages() from the TypeScript SDK. Reads a single
session JSONL file fully, reconstructs the conversation chain via
parentUuid links, and returns user/assistant messages in chronological
order.

- Add SessionMessage dataclass to types.py (type, uuid, session_id,
  message, parent_tool_use_id)
- Add get_session_messages() to _internal/sessions.py, reusing existing
  path sanitization, config dir resolution, and worktree detection
  helpers from list_sessions
- Chain algorithm: find terminal entries (no children), walk each back
  to nearest user/assistant leaf, pick main-chain leaf with highest
  file position, then walk leaf→root via parentUuid and reverse
- Filters: isMeta, isSidechain, teamName; keeps isCompactSummary
  (matches VS Code IDE behavior post-compaction)
- Supports limit/offset pagination
- Returns empty list for invalid UUID or missing session (no raise)
- 21 new tests covering chain building, filtering, pagination, cycles
@qing-ant qing-ant merged commit 2c418f7 into main Mar 3, 2026
9 checks passed
@qing-ant qing-ant deleted the qing/list-sessions branch March 3, 2026 18:59
qing-ant added a commit that referenced this pull request Mar 18, 2026
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().
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