fix(cli): JSON-escape TS_HOOKS_DIR substitution so ts init works on Windows#36
Closed
FunLay123 wants to merge 1 commit into
Closed
fix(cli): JSON-escape TS_HOOKS_DIR substitution so ts init works on Windows#36FunLay123 wants to merge 1 commit into
ts init works on Windows#36FunLay123 wants to merge 1 commit into
Conversation
dad2627 to
9f4092c
Compare
… Windows
Problem
-------
`_load_hook_bundles` substitutes the {{TS_HOOKS_DIR}} placeholder into
shipped hook configs by a plain string replace, then runs json.loads
on the result. The placeholder sits inside a JSON string literal:
"command": "/usr/bin/python3 {{TS_HOOKS_DIR}}/tool_capture_hook.py"
On POSIX the resolved path has no characters that need JSON-escaping,
so this works. On Windows the resolved path looks like
E:\Misc\repo\token-savior\hooks
and the unescaped backslashes produce invalid \escape sequences inside
the JSON string, making json.loads raise
JSONDecodeError: Invalid \escape: ...
The net effect: `ts init` was unusable on Windows for every supported
agent (claude, cursor, gemini, codex), since every shipped hook config
references {{TS_HOOKS_DIR}}.
Fix
---
Pass the resolved path through json.dumps and strip the surrounding
quotes before substituting. That yields a value whose contents are
already valid JSON-string content (backslashes doubled, quotes
escaped), so the substituted text remains parseable JSON, and
json.loads decodes the doubled backslashes back into a single \
in the final dict.
POSIX behavior is unchanged -- for paths without characters that need
escaping, json.dumps(s)[1:-1] == s.
9f4092c to
ae25778
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ts initis currently unusable on Windows for every supported agent (claude, cursor, gemini, codex). Root cause is in_load_hook_bundles(src/token_savior/cli_init/__init__.py).Problem
The function substitutes the
{{TS_HOOKS_DIR}}placeholder into shipped hook configs by a plain string replace, then runsjson.loadson the result. The placeholder sits inside a JSON string literal:On POSIX the resolved path has no characters that need JSON-escaping, so this works. On Windows the resolved path looks like
and the unescaped backslashes produce invalid
\escapesequences inside the JSON string, makingjson.loadsraise:Reproduced locally — without the fix,
_load_hook_bundlesraises for all 4 supported agents.Fix
Pass the resolved path through
json.dumpsand strip the surrounding quotes before substituting. The result is valid JSON-string content (backslashes doubled, quotes escaped), so the substituted text remains parseable JSON, andjson.loadsdecodes the doubled backslashes back into a single\in the final dict.POSIX behavior is unchanged — for paths without characters that need escaping,
json.dumps(s)[1:-1] == s.Test plan
JSONDecodeErroron Windows for claude / cursor / gemini / codex hook bundles_load_hook_bundlesreturns valid dicts for all 4 agents on Windows; substituted command strings contain correct pathspytest tests/test_cli_init.py(existingtest_load_hook_bundles_claude_real_repoandtest_apply_bundles_combines_post_and_preexercise this code path — they previously could only have failed on a Windows CI runner)json.dumps(s)[1:-1] == sfor backslash-free strings)