Skip to content

Match the Claude CLI's project-dir encoding when checking a resume target - #522

Merged
pufit merged 1 commit into
mainfrom
alex/resume-target-encoding
Oct 20, 2026
Merged

pufit merged 1 commit into
mainfrom
alex/resume-target-encoding

Conversation

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

What

ClaudeBackend.validate_resume_target now builds the history directory name with the same rule as the Claude CLI. Before Nerve resumes a stored SDK session, it checks that ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl exists. The check encoded the cwd by replacing only / with -.

Why

The CLI replaces every character outside [a-zA-Z0-9] with -. For a workspace path that contains another character, for example _ or ., the check looks in a directory that does not exist and returns False. The engine then drops the stored session ID and starts a new conversation on each client rebuild: model change, dead client, restart. Seen with a workspace at /tmp/<name>_/ws: the CLI wrote to -tmp-<name>--ws, and the check looked in -tmp-<name>_-ws. Paths such as ~/nerve-workspace are not affected.

How

The rule comes from the CLI bundled with claude-agent-sdk 0.2.161:

function k(e){return e.replace(/[^a-zA-Z0-9]/g,"-")}
function AR(e){let n=k(e);if(n.length<=200)return n;return`${n.slice(0,200)}-${hash(e)}`}
  • _encode_project_dir() applies the same regex. The JS regex works on UTF-16 code units, so a character outside the Basic Multilingual Plane becomes --. node gives the same output for the test inputs.
  • When the encoded name is longer than 200 characters, the CLI cuts it and adds -<hash>. When the CLI looks up a session, it lists the projects directory and matches on the cut name plus -. _long_project_dir_names() does the same, so Nerve does not compute the hash. A missing projects directory gives False, the same as for a short name.
  • The realpath-first, unresolved-path-second order and the fail-open except do not change.

Tests

tests/test_engine.py::TestValidateResumeTarget:

  • New: a path with _ and . (mocked and on a real filesystem), a character outside the BMP, a name over 200 characters with a suffixed directory, a sibling that does not match, and a missing projects directory.
  • The three symlink tests built the expected directory name with replace("/", "-"). pytest's tmp_path contains _, so they now use the CLI rule.
  • Against the old implementation, 6 of the 13 tests fail. With the change, all pass.

Full backend suite: 5753 passed, 38 skipped.

🤖 Generated with Claude Code

The CLI names a session's history directory after the cwd with every
character outside [a-zA-Z0-9] replaced by '-'. The resume check replaced
only '/', so for a workspace path with '_' or '.' it looked in a
directory that does not exist, and every client rebuild started a new
conversation.

Use the CLI's regex, per UTF-16 code unit. For a name over 200
characters the CLI cuts it and adds '-<hash>'; match on the cut name
plus '-', as the CLI does when it looks up a session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alex-clickhouse
alex-clickhouse marked this pull request as ready for review October 17, 2026 09:34
@alex-clickhouse
alex-clickhouse requested a review from pufit October 17, 2026 09:34
@pufit
pufit merged commit 5f1259d into main Oct 20, 2026
4 checks passed
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