Skip to content

feat(sdk): Store Claude SDK sessions in .warden/sessions/#193

Open
dcramer wants to merge 13 commits intomainfrom
claude/slack-session-h4ekd
Open

feat(sdk): Store Claude SDK sessions in .warden/sessions/#193
dcramer wants to merge 13 commits intomainfrom
claude/slack-session-h4ekd

Conversation

@dcramer
Copy link
Member

@dcramer dcramer commented Feb 20, 2026

Capture SDK session transcripts by moving session files from Claude SDK's
internal storage into .warden/sessions/ after each run. Keeps all Warden
artifacts under .warden/ and lets downstream tooling inspect or replay
session data.

Session storage is enabled by default. Files are moved (copy+delete) rather
than renamed to handle cross-filesystem boundaries in Docker/CI. Expired
sessions are auto-pruned using the same retention pattern as logs.

# warden.toml
[sessions]
enabled = true           # default
directory = ".warden/sessions"  # default
cleanup = "auto"         # "auto" | "ask" | "never" (default: "auto")
retentionDays = 7        # default

warden init now gitignores .warden/ (covering both logs and sessions).

Fixes #188

Add session storage functionality to capture SDK conversations
for debugging, analysis, and replay purposes.

Changes:
- Add SessionCollector class to capture SDK messages during query execution
- Store sessions organized by timestamp and session ID
- Add utilities for listing, reading, and pruning old sessions
- Integrate session capture into the SDK runner with opt-in configuration
- Add .warden/sessions/ to gitignore
- Add comprehensive tests for session storage

Closes #188

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

https://claude.ai/code/session_012gZ8yStobN3XJ4gaL2YxMH
@dcramer dcramer changed the title Add session storage for SDK query execution feat(sdk): Store Claude SDK sessions in .warden/sessions/ Feb 20, 2026
Rework session storage to move the JSONL files Claude Code writes
internally (~/.claude/projects/<hash>/<uuid>.jsonl) to .warden/sessions/
after each query, rather than creating a parallel capture from the stream.

- session.ts: replace SessionCollector with moveSession() + getClaudeProjectDir()
- config/schema.ts: add [sessions] config block (enabled, directory)
- analyze.ts: call moveSession() post-query using result uuid
- executor.ts, main.ts: pass config.sessions into runnerOptions

Configure via warden.toml:

  [sessions]
  enabled = true

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>

https://claude.ai/code/session_012gZ8yStobN3XJ4gaL2YxMH
- Replace renameSync with copyFileSync+unlinkSync to handle EXDEV errors
  when home directory and repo are on different filesystems (Docker/CI)
- Default session storage to enabled
- Gitignore .warden/ (not just .warden/logs/) on init

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add retention-based session cleanup (7-day default, auto mode)
- Rename cleanupLogs to cleanupArtifacts for shared log/session use
- Default session storage to enabled
- Gitignore .warden/ on init (covers logs and sessions)
- Use copy+delete for cross-device moves (EXDEV)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Only pass enabled/directory to SDK (strip cleanup/retention fields)
- Defensive statSync in listSessions sort for race conditions
- Add .warden/ to repo gitignore
- Improve readability of init gitignore check

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When [sessions] is omitted from warden.toml, config.sessions is
undefined. Previously this meant sessions were silently disabled.
Now all entry points default to { enabled: true } so sessions work
out of the box without any config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of relying on resultMessage.uuid (unavailable on abort/error),
snapshot Claude's project dir before each executeQuery and diff in a
finally block to capture any new session files. This ensures sessions
are stored even when runs are cancelled or fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove moveSession (replaced by snapshot-based moveNewSessions)
- Remove findExpiredSessions and pruneOldSessions (cleanup uses
  cleanupArtifacts from log-cleanup.ts)
- Fix session cleanup to use resolveSessionsDir for correct absolute
  path handling
- Add existsSync guard in moveNewSessions for concurrent hunk races
- Remove tests for deleted functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handle stat failures individually for each file so the comparator
remains consistent with the sort contract (compare(a,b) = -compare(b,a)).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace specific .warden/logs/ and .warden/sessions/ entries with a
single .warden/ catch-all and move it to the correct section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move session capture from per-hunk to per-skill level so all SDK
  processes have fully exited and flushed before copying files
- Add session handling to CLI task runner (tasks.ts) which was missing it
- Skip empty (0-byte) session files as defense against race conditions
- Name session files as <skill>-<shortId>-<datetime>.jsonl
- Name log files as <shortId>-<datetime>.jsonl (id-first, consistent)
- Show log file path after summary at normal verbosity
- Add reporter.dim() for subtle output visible without --debug
- Replace blind catches with logged warnings via Sentry logger
- Fix ?? to || for empty string shortUuid fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
error: err instanceof Error ? err.message : String(err),
skill: displayName,
});
}
Copy link

Choose a reason for hiding this comment

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

Concurrent skills race on session file snapshot

Low Severity

When multiple skills run concurrently via runComposedSkillTasks (which launches all tasks in parallel), each runSkillTask snapshots the same shared Claude project directory before work begins. Because all snapshots capture the same initial state, the first skill to complete its moveNewSessions call moves ALL new session files (including those from other concurrent skills) under its own prefix. Remaining skills find nothing to move, causing session misattribution.

Fix in Cursor Fix in Web

The log path was displayed after the summary even when the write failed,
showing both a warning and a path to a non-existent file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Dead code — only referenced in tests, never called in production,
and there are no external consumers of this package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Store Claude SDK sessions in .warden/sessions

2 participants

Comments