Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions hooks/session-start
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,37 @@ escape_for_json() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
# Bash strings cannot carry NUL; escape every other C0 control.
s="${s//$'\001'/\\u0001}"
s="${s//$'\002'/\\u0002}"
s="${s//$'\003'/\\u0003}"
s="${s//$'\004'/\\u0004}"
s="${s//$'\005'/\\u0005}"
s="${s//$'\006'/\\u0006}"
s="${s//$'\007'/\\u0007}"
s="${s//$'\b'/\\b}"
s="${s//$'\n'/\\n}"
s="${s//$'\v'/\\u000b}"
s="${s//$'\f'/\\f}"
s="${s//$'\r'/\\r}"
s="${s//$'\016'/\\u000e}"
s="${s//$'\017'/\\u000f}"
s="${s//$'\020'/\\u0010}"
s="${s//$'\021'/\\u0011}"
s="${s//$'\022'/\\u0012}"
s="${s//$'\023'/\\u0013}"
s="${s//$'\024'/\\u0014}"
s="${s//$'\025'/\\u0015}"
s="${s//$'\026'/\\u0016}"
s="${s//$'\027'/\\u0017}"
s="${s//$'\030'/\\u0018}"
s="${s//$'\031'/\\u0019}"
s="${s//$'\032'/\\u001a}"
s="${s//$'\033'/\\u001b}"
s="${s//$'\034'/\\u001c}"
s="${s//$'\035'/\\u001d}"
s="${s//$'\036'/\\u001e}"
s="${s//$'\037'/\\u001f}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
Expand Down
29 changes: 29 additions & 0 deletions hooks/session-start-codex
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,37 @@ escape_for_json() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
# Bash strings cannot carry NUL; escape every other C0 control.
s="${s//$'\001'/\\u0001}"
s="${s//$'\002'/\\u0002}"
s="${s//$'\003'/\\u0003}"
s="${s//$'\004'/\\u0004}"
s="${s//$'\005'/\\u0005}"
s="${s//$'\006'/\\u0006}"
s="${s//$'\007'/\\u0007}"
s="${s//$'\b'/\\b}"
s="${s//$'\n'/\\n}"
s="${s//$'\v'/\\u000b}"
s="${s//$'\f'/\\f}"
s="${s//$'\r'/\\r}"
s="${s//$'\016'/\\u000e}"
s="${s//$'\017'/\\u000f}"
s="${s//$'\020'/\\u0010}"
s="${s//$'\021'/\\u0011}"
s="${s//$'\022'/\\u0012}"
s="${s//$'\023'/\\u0013}"
s="${s//$'\024'/\\u0014}"
s="${s//$'\025'/\\u0015}"
s="${s//$'\026'/\\u0016}"
s="${s//$'\027'/\\u0017}"
s="${s//$'\030'/\\u0018}"
s="${s//$'\031'/\\u0019}"
s="${s//$'\032'/\\u001a}"
s="${s//$'\033'/\\u001b}"
s="${s//$'\034'/\\u001c}"
s="${s//$'\035'/\\u001d}"
s="${s//$'\036'/\\u001e}"
s="${s//$'\037'/\\u001f}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
Expand Down
43 changes: 43 additions & 0 deletions tests/hooks/test-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ make_home() {
printf '%s\n' "$home"
}

make_plugin_fixture() {
local name="$1"
local hook_name="$2"
local skill_content="$3"
local source_hook="$REPO_ROOT/hooks/$hook_name"
local fixture_root="$TEST_ROOT/$name/plugin"

mkdir -p "$fixture_root/hooks" "$fixture_root/skills/using-superpowers"
cp "$source_hook" "$fixture_root/hooks/$hook_name"
chmod +x "$fixture_root/hooks/$hook_name"
printf '%s' "$skill_content" > "$fixture_root/skills/using-superpowers/SKILL.md"
printf '%s\n' "$fixture_root"
}

assert_command_output() {
local description="$1"
local shape="$2"
Expand Down Expand Up @@ -232,6 +246,35 @@ assert_command_output \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$CODEX_HOOK_UNDER_TEST"

control_text="$(printf 'control chars: \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 end')"

control_fixture="$(make_plugin_fixture control-chars session-start "$control_text")"
control_home="$(make_home control-chars)"
assert_command_output \
"SessionStart escapes JSON control characters in skill content" \
"nested" \
"$control_text" \
"" \
"$control_home" \
CLAUDE_PLUGIN_ROOT="$control_fixture" \
bash "$control_fixture/hooks/session-start"

codex_control_fixture="$(make_plugin_fixture codex-control-chars session-start-codex "$control_text")"
codex_control_home="$(make_home codex-control-chars)"
codex_control_data="$TEST_ROOT/codex-control-chars/data"
mkdir -p "$codex_control_data"
assert_command_output \
"Codex SessionStart escapes JSON control characters in skill content" \
"nested" \
"$control_text" \
"" \
"$codex_control_home" \
PLUGIN_DATA="$codex_control_data" \
CLAUDE_PLUGIN_DATA="$codex_control_data" \
PLUGIN_ROOT="$codex_control_fixture" \
CLAUDE_PLUGIN_ROOT="$codex_control_fixture" \
bash "$codex_control_fixture/hooks/session-start-codex"

if [[ "$FAILURES" -gt 0 ]]; then
echo "STATUS: FAILED ($FAILURES failure(s))"
exit 1
Expand Down