Skip to content

feat: add tag_session with Unicode sanitization#670

Merged
qing-ant merged 3 commits intomainfrom
qing/sdk-tag-session-py
Mar 12, 2026
Merged

feat: add tag_session with Unicode sanitization#670
qing-ant merged 3 commits intomainfrom
qing/sdk-tag-session-py

Conversation

@qing-ant
Copy link
Contributor

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

Stacked on #668 (rename_session).

Changes

  • NEW: tag_session(session_id, tag, directory=None) — appends a {type:'tag',tag:<tag>,sessionId:<id>} JSONL entry. list_sessions() reads the LAST tag from the file tail — most recent wins.
  • Passing None appends an empty-string tag entry ({"tag":""}) which readers treat as cleared.

Port notes

Unicode sanitization (for CLI filter compat):

  • Python's re module doesn't support \p{Cf} etc. without the third-party regex module, so _sanitize_unicode() uses unicodedata.category(c) not in {"Cf","Co","Cn"} per-character — semantically equivalent to TS's /[\p{Cf}\p{Co}\p{Cn}]/gu regex (and more reliable than TS's own ES-engine fallback).
  • Applies NFKC normalization + explicit ranges (zero-width chars, directional marks, BOM, private-use area) iteratively until stable (max 10 passes).

Other behavior:

  • Tag is .strip()ed before storing; whitespace-only tags rejected (ValueError).
  • Tags that are pure invisible characters (e.g. only zero-width spaces) are rejected after sanitization.
  • Reuses _append_to_session from the rename_session PR (worktree fallback, 0-byte stub skip, O_APPEND without O_CREAT).
  • Add tag_session() for tagging sessions (with Unicode sanitization)

Tests

17 new tests (TestTagSession, TestSanitizeUnicode). 298 total pass. Ruff + mypy clean.

Ports TS SDK renameSessionImpl from qing/sdk-rename-session (PR #21660).

Appends a {type:'custom-title',customTitle:<title>,sessionId:<id>} JSONL
entry to the session file. list_sessions reads the LAST custom-title from
the file tail, so repeated calls are safe — the most recent wins.

Key behaviors ported:
- Validates UUID; rejects invalid session_id with ValueError
- Trims title before storing; rejects empty/whitespace-only titles
- Uses os.open with O_WRONLY | O_APPEND (no O_CREAT) so missing files
  fail with ENOENT — no TOCTOU pre-check. CPython handles O_APPEND
  correctly on all platforms (no Bun/Windows workaround needed).
- Skips 0-byte stubs during search (matches reader search protocol)
- Worktree fallback when directory is provided
- Searches all project directories when directory is omitted
- Compact JSON output (separators=(',', ':')) matching CLI format

Tests: 281 passed. Ruff + mypy clean.
Ports TS SDK tagSessionImpl from qing/sdk-tag-session (PR #21662).

Appends a {type:'tag',tag:<tag>,sessionId:<id>} JSONL entry. list_sessions
reads the LAST tag from the file tail — most recent wins. Passing None
appends an empty-string tag entry (clears the tag).

Key behaviors ported:
- Validates UUID; rejects invalid session_id with ValueError
- Trims tag before storing; rejects empty/whitespace-only tags
- Unicode sanitization: strips zero-width chars, directional marks,
  BOM, private-use characters, and applies NFKC normalization
  (ported from TS partiallySanitizeUnicode) for CLI filter compat
- None appends empty-string tag entry which readers treat as cleared
- Compact JSON output matching CLI format
- Reuses _append_to_session (worktree fallback, 0-byte stub skip,
  O_APPEND without O_CREAT)

Python note: uses unicodedata.category() for Cf/Co/Cn stripping since
Python's re module doesn't support \p{} escapes without the third-party
regex module.

Tests: 310 passed. Ruff + mypy clean.
@qing-ant qing-ant force-pushed the qing/sdk-tag-session-py branch from 1e2839d to 034bfbe Compare March 11, 2026 01:09
@qing-ant qing-ant changed the base branch from qing/sdk-delete-session-py to qing/sdk-rename-session-py March 11, 2026 01:09
dicksontsai
dicksontsai previously approved these changes Mar 12, 2026
Base automatically changed from qing/sdk-rename-session-py to main March 12, 2026 19:46
@qing-ant qing-ant dismissed dicksontsai’s stale review March 12, 2026 19:46

The base branch was changed.

# Conflicts:
#	src/claude_agent_sdk/__init__.py
#	src/claude_agent_sdk/_internal/session_mutations.py
#	tests/test_session_mutations.py
@qing-ant qing-ant enabled auto-merge (squash) March 12, 2026 22:05
@qing-ant qing-ant merged commit 2513c45 into main Mar 12, 2026
9 checks passed
@qing-ant qing-ant deleted the qing/sdk-tag-session-py branch March 12, 2026 22:23
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