Skip to content

list_sessions() returns created_at=None when first JSONL record lacks a timestamp field #904

@peter216

Description

@peter216

Summary

list_sessions() returns created_at=None for sessions whose first JSONL record does not contain a timestamp field, even though later records in the same file do.

Root cause

In _internal/sessions.py, _parse_session_lite() (around line 461) calls:

first_timestamp = _extract_json_string_field(first_line, "timestamp")

_extract_json_string_field does a raw substring search for "timestamp":" anywhere in first_line. This means:

  • Sessions whose first record is permission-mode (e.g. {"type":"permission-mode","permissionMode":"acceptEdits","sessionId":"..."}) have no timestamp anywhere in that line → created_at stays None.
  • Sessions whose first record is file-history-snapshot incidentally get a valid created_at because the snapshot embeds a nested timestamp: "snapshot":{"messageId":"...","timestamp":"2026-04-27T00:08:..."} — the raw search matches the nested field, not a top-level one.

So created_at is populated based on an accidental nested-field match in one record type, and absent for another, rather than reliably reflecting the session start time.

Steps to reproduce

  1. Open a Claude Code session that begins with a permission-mode record as the first line in its JSONL file.
  2. Call list_sessions().
  3. Observe session.created_at is None for that session.

You can verify the first-line type with:

import json
with open(f"~/.claude/projects/.../SESSION_ID.jsonl") as f:
    print(json.loads(f.readline()).get("type"))
    # "permission-mode" → created_at will be None
    # "file-history-snapshot" → created_at may be incidentally populated via nested timestamp

Expected behavior

created_at should reliably reflect the timestamp of the first user interaction (or session start), regardless of which record type appears first in the file.

Suggested fix

Instead of reading only first_line, scan through the lines in head until a record with a top-level timestamp field is found — or specifically look for the first user or assistant type record. Alternatively, use the top-level timestamp field and skip records that lack it, rather than matching incidental nested occurrences.

Environment

  • claude-agent-sdk version: 0.1.63
  • Python: 3.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions