Skip to content

feat: transfer task list across agent sessions via CLAUDE_CODE_TASK_LIST_ID#319

Merged
Wirasm merged 4 commits into
mainfrom
kild/296-task-list-transfer
Feb 10, 2026
Merged

feat: transfer task list across agent sessions via CLAUDE_CODE_TASK_LIST_ID#319
Wirasm merged 4 commits into
mainfrom
kild/296-task-list-transfer

Conversation

@Wirasm

@Wirasm Wirasm commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Set CLAUDE_CODE_TASK_LIST_ID env var when spawning Claude agents so task lists persist across session stop/resume cycles
  • ID is generated deterministically from the session ID (kild-{session_id} with / sanitized to -), stored in session JSON, and injected into both terminal and daemon spawn paths
  • On resume: reuses existing task list ID so tasks survive restarts
  • On fresh open: generates new task list ID for a clean start
  • On destroy: cleans up ~/.claude/tasks/{task_list_id}/ directory

Post-review fixes

  • Fixed shell injection in terminal export commands (env var values now single-quoted)
  • Sanitized / to - in task list IDs for flat directory names
  • Replaced full session re-save in agent_status with patch_session_json_field() to prevent older binaries from dropping new fields
  • Extracted cleanup_task_list() for testability
  • Added tests for daemon env var injection, destroy cleanup, slash sanitization, and JSON field preservation
  • Updated docs (save-tasks.md, CLAUDE.md)

Related issues

Test plan

  • cargo fmt --check passes
  • cargo clippy --all -- -D warnings passes
  • cargo test --all passes (1,506 tests, 0 failures)
  • cargo build --all passes
  • Unit tests for generate_task_list_id() and task_list_env_vars()
  • Test for slash sanitization in task list ID
  • Tests for build_daemon_create_request with Some(task_list_id), non-Claude agents, and None
  • Tests for cleanup_task_list (removal, nonexistent dir, None guard)
  • Test for patch_session_json_field preserving unknown fields
  • Non-Claude agents get no task list env var
  • Old session JSON without task_list_id deserializes to None (serde default)
  • E2E: task list created in kild, visible after stop/reopen via status bar and TaskList tool

Closes #296

@Wirasm

Wirasm commented Feb 10, 2026

Copy link
Copy Markdown
Owner Author

PR Review Summary

Critical Issues (1 found)

Agent Issue Location
silent-failure-hunter Shell injection risk: Unquoted env var values in export command construction. If task_list_id contains shell metacharacters (spaces, semicolons, backticks), arbitrary command execution is possible. Values should be single-quoted. handler.rs:252, handler.rs:881

Important Issues (3 found)

Agent Issue Location
pr-test-analyzer Task list cleanup on destroy has zero test coverage — path construction, removal, error handling all untested destroy.rs:333-358
pr-test-analyzer Daemon env var injection untested — all existing build_daemon_create_request tests pass None for task_list_id, so the new branch is never exercised handler.rs:1101-1108
pr-test-analyzer create_session integration not tested — no test verifies Claude agent gets a task_list_id generated and persisted in session JSON handler.rs:178-187

Suggestions (6 found)

Agent Suggestion Location
pr-test-analyzer Terminal command prepending with export syntax not tested handler.rs:244-258
pr-test-analyzer Task list ID reuse on resume vs fresh generation not integration tested handler.rs:801-812
pr-test-analyzer Session serialization round-trip doesn't exercise task_list_id field types.rs
comment-analyzer task_list_id field doc could mention None for non-Claude agents types.rs:176-184
comment-analyzer Terminal export comment could explain architectural difference from daemon path handler.rs:244
type-design-analyzer Consider extracting CLAUDE_CODE_TASK_LIST_ID as a named constant resume.rs:48

Strengths

  • Clean separation of concerns: ID generation in agents/resume.rs, lifecycle in handler.rs, cleanup in destroy.rs
  • Well-designed types: pragmatic Option<String> with #[serde(default)] for backward compatibility (7.75/10 type design score)
  • Accurate, concise documentation on all new functions and fields
  • Unit tests for helper functions (generate_task_list_id, task_list_env_vars) cover both positive and negative cases
  • All existing test constructors updated for new Session::new() parameter

Documentation Updates

  • .claude/commands/save-tasks.md — Rewrote for automatic task list transfer (removed stale manual instructions)
  • CLAUDE.md — Added task list cleanup to destroy.rs integration points

Verdict

NEEDS FIXES — 1 critical shell injection issue must be resolved before merge. Test coverage gaps should be addressed for key code paths (destroy cleanup, daemon env var injection).

Recommended Actions

  1. Fix shell injection: single-quote env var values in terminal export commands, or validate task_list_id format
  2. Add test for build_daemon_create_request with Some(task_list_id) to verify daemon env var injection
  3. Add test for task list cleanup on destroy
  4. Consider adding integration test for create_session with Claude agent verifying task_list_id generation

…IST_ID

Set CLAUDE_CODE_TASK_LIST_ID env var when spawning Claude agents so task
lists persist across session stop/resume cycles. The ID is generated
deterministically from the session ID, stored in the session JSON, and
injected into both terminal and daemon spawn paths.

- Add generate_task_list_id() and task_list_env_vars() to agents/resume
- Add task_list_id field to Session struct
- Inject env var in create_session (terminal + daemon paths)
- Inject env var in open_session (resume reuses ID, fresh open generates new)
- Clean up ~/.claude/tasks/{task_list_id}/ on destroy

Closes #296
- Update save-tasks.md to reflect automatic task list persistence via --resume
- Add task list cleanup to destroy.rs integration points in CLAUDE.md
- Remove stale manual task list management instructions
- Fix shell injection in terminal export commands by single-quoting env
  var values with proper escaping for embedded single quotes
- Sanitize task list ID by replacing / with - to produce flat directory
  names compatible with Claude Code's task storage
- Prevent agent-status from dropping unknown session fields by using
  targeted JSON patching instead of full Session deserialize/serialize
- Extract cleanup_task_list() for testability
- Add tests for daemon env var injection, destroy cleanup, slash
  sanitization, and JSON field preservation
@Wirasm

Wirasm commented Feb 10, 2026

Copy link
Copy Markdown
Owner Author

PR Review Summary

Critical Issues (1 found)

Agent Issue Location
silent-failure-hunter patch_session_json_field silently succeeds when session JSON is not an object — field update is silently skipped, no error returned, caller thinks heartbeat was updated persistence.rs:179-181

Details: The if let Some(obj) = json.as_object_mut() pattern silently does nothing when the JSON root is not an object (e.g., corrupted file with [], null, or a primitive). The function returns Ok(()) even though no field was updated. This means agent_status.rs thinks last_activity was patched when it wasn't, violating the "No Silent Failures" project principle.

Fix: Replace with:

let obj = json.as_object_mut().ok_or_else(|| SessionError::IoError {
    source: std::io::Error::new(
        std::io::ErrorKind::InvalidData,
        "session JSON root is not an object",
    ),
})?;
obj.insert(field.to_string(), value);

Important Issues (2 found)

Agent Issue Location
silent-failure-hunter cleanup_task_list logs at error! level but treats failure as non-fatal — inconsistent severity (should be warn!) destroy.rs:90
comment-analyzer "Existing path" comment is misleading — should be "Terminal path" to match daemon path naming convention handler.rs:243, 872

Suggestions (5 found)

Agent Suggestion Location
type-design-analyzer Consider SessionField enum for patch_session_json_field field parameter to prevent typos persistence.rs:168
pr-test-analyzer Add test for patch_session_json_field with non-object JSON (validates the critical fix) persistence.rs
pr-test-analyzer Add test for terminal export format string construction handler.rs:250-254
comment-analyzer Remove "(existing behavior)" from terminal path comment in open_session handler.rs:872
comment-analyzer Add atomic write note to patch_session_json_field docstring persistence.rs:161

Strengths

  • Clean shell escaping in terminal export commands (single-quote wrapping with proper '\'' escaping)
  • Path traversal prevention: branch validation rejects .., / sanitized to - in task list IDs
  • Atomic file operations via temp file + rename in JSON patching
  • Excellent backward-compatibility story via patch_session_json_field preserving unknown fields
  • Strong test coverage: 12 new tests covering ID generation, env var injection, cleanup, and JSON patching
  • Well-structured docstrings on all new public functions with lifecycle documentation

Type Design Assessment

Type Score Status
Session.task_list_id 7/10 Adequate
generate_task_list_id() 6/10 Adequate
task_list_env_vars() 7/10 Adequate
patch_session_json_field() 5/10 Needs improvement (stringly-typed field, silent failure)
Session::new() (15 params) 6/10 Watch for future growth

Verdict

NEEDS ONE FIX — The silent failure in patch_session_json_field must be addressed. Everything else is ready.

- Return error from patch_session_json_field when JSON root is not an
  object instead of silently succeeding
- Add test for non-object JSON rejection
- Change cleanup_task_list log level from error! to warn! (non-fatal)
- Fix misleading comments: "Existing path" → "Terminal path"
- Add atomic write note to patch_session_json_field docstring
@Wirasm Wirasm merged commit bd6a034 into main Feb 10, 2026
6 checks passed
@Wirasm Wirasm deleted the kild/296-task-list-transfer branch February 10, 2026 21:49
Wirasm added a commit that referenced this pull request Feb 11, 2026
sync_daemon_session_status() was using save_session_to_file() which
round-trips through the Session struct, silently dropping fields from
newer binary versions (e.g., task_list_id). This is the same class of
bug fixed in agent_status.rs by PR #319.

Changes:
- Add patch_session_json_fields() for atomic multi-field JSON patching
- Replace save_session_to_file() with field-level patches in sync_daemon_session_status
- Add test verifying unknown fields survive multi-field patching

Fixes #321
Wirasm added a commit that referenced this pull request Feb 11, 2026
sync_daemon_session_status() was using save_session_to_file() which
round-trips through the Session struct, silently dropping fields from
newer binary versions (e.g., task_list_id). This is the same class of
bug fixed in agent_status.rs by PR #319.

Changes:
- Add patch_session_json_fields() for atomic multi-field JSON patching
- Replace save_session_to_file() with field-level patches in sync_daemon_session_status
- Add test verifying unknown fields survive multi-field patching

Fixes #321
Wirasm added a commit that referenced this pull request Feb 11, 2026
sync_daemon_session_status() was using save_session_to_file() which
round-trips through the Session struct, silently dropping fields from
newer binary versions (e.g., task_list_id). This is the same class of
bug fixed in agent_status.rs by PR #319.

Changes:
- Add patch_session_json_fields() for atomic multi-field JSON patching
- Replace save_session_to_file() with field-level patches in sync_daemon_session_status
- Add test verifying unknown fields survive multi-field patching

Fixes #321
Wirasm added a commit that referenced this pull request Feb 11, 2026
* investigate issue #321: sync_daemon_session_status field-dropping bug

* fix: use targeted JSON patching in sync_daemon_session_status (#321)

sync_daemon_session_status() was using save_session_to_file() which
round-trips through the Session struct, silently dropping fields from
newer binary versions (e.g., task_list_id). This is the same class of
bug fixed in agent_status.rs by PR #319.

Changes:
- Add patch_session_json_fields() for atomic multi-field JSON patching
- Replace save_session_to_file() with field-level patches in sync_daemon_session_status
- Add test verifying unknown fields survive multi-field patching

Fixes #321
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.

feat: transfer task list across agent sessions via CLAUDE_CODE_TASK_LIST_ID

1 participant