Release v3.62.1 - Supervisor Injection Reliability and Upgrade-Path Fixes
Release Date: 2026-09-07
Type: Patch Release
Summary
This release fixes a field-urgent bug where the ccy PTY supervisor's
/compact injection could land unsubmitted in Claude Code's input box, and
closes out the residue of the v3.62.0 upgrade-path work: six field-reported
upgrade defects plus the design-level findings that surfaced while fixing
them, including a config-preservation baseline bug that could silently keep
a changed default from ever reaching a user who had accepted the old one.
It also closes further gaps in guard behaviour introduced in v3.62.0
(curl_pipe_shell's quoted-heredoc exemption, and project_containment,
which is itself new in v3.62.0) found by continued probing of the shipped
code. No new features. No breaking changes — but several commands that were
previously ALLOWED are now DENIED; see Upgrade Instructions.
Highlights
- Supervisor
/compactcould sit unsubmitted in the input box (Plan
00339). Measured against a real Claude Code v2.1.263 over a PTY: a
large injected burst triggers Claude Code's paste detection, and a
carriage return inside pasted text is read as a literal newline rather
than a submit. The payload is now framed in explicit bracketed-paste
markers so the trailing Enter is unambiguously a keypress, plus a second
stall remedy (an alternating bare Enter) for a line left sitting
unsubmitted in the input box — the opposite cause to the one[esc]
already handled, which is a command Claude Code QUEUED behind an
in-flight turn. - Six field-reported v3.61.0 -> v3.62.0 upgrade defects, fixed with a
failing test written first (commitsb1217789,a9866261; residue
tracked in Plan 00336): a config-merge bug that
silently dropped whole config sections, a crash on any config value
containing a backslash, a version-parsing bug that skipped breaking-change
warnings, a stale-code bug in the upgrade orchestrator, two documentation
overstatements. - The config-preservation diff baseline was comparing against the wrong
side of the version boundary on the documented (Layer 1) upgrade path,
so a changed default could silently never reach a user who had simply
accepted the previous one — not a one-release accident, an ongoing effect
on every affected client. - Two more gaps closed in guard behaviour introduced in v3.62.0:
curl_pipe_shell's
quoted-heredoc exemption is now an allowlist of safe data-sink receivers
(closingssh host <<EOFand the whole word-expansion receiver family),
andproject_containment's relative-destination resolution now agrees
with its own redirect route.
Changes
See CHANGELOG.md for the full,
categorised list of changes in this release.
Security
curl … | python3was allowed by the priority-10 curl-pipe guard. The
interpreter list holds bare names and the pattern ended in\b, which
cannot follow a name ending in a digit — so the spelling real install
instructions use, and often the only one present on a modern system, went
straight through./usr/bin/python3,python3.12,ruby3andperl5
likewise. Now matched with an optional numeric version suffix.curl_pipe_shell: the quoted-heredoc exemption enumerated EXECUTING
receivers, so any receiver nobody had thought to list defaulted to being
granted the exemption. That enumeration had already failed three times;
probing the shipped handler found a fourth (ssh host <<EOF, which
executes the body on the remote host). Inverted to an allowlist of
DATA-SINK receivers instead — an unrecognised receiver now withholds the
exemption, closingsshand the entire word-expansion family
($SHELL,b$ash, ...) in one change, by construction rather than by
enumeration.- Three database clients had been listed as data sinks, and all three run
shell commands from stdin (sqlite3.shell,psql\!,mysql
system), so a heredoc body naming one was blanked before the scan.
Removed from the allowlist and recorded as executors. - A recognised sink whose OUTPUT is piped into an interpreter now withholds
the exemption too —(cat <<'X' … X) | bashfed a sink and then executed
the body anyway. project_containment: relative-destination resolution forcurl -o,
wget -O,mkdir -p,tar -cfandrsyncdisagreed with the redirect
route on identical effect, and a trailing-slash copy destination
(cp README.md /tmp/) was not recognised as a destination at all. Both
now resolve against the same cwd.
See CHANGELOG.md for full detail.
Breaking Changes
None. No handlers, configuration keys, or CLI subcommands were removed or
renamed in this release, and no default behaviour changed in a way that
would fail previously-valid configuration.
Why this is a PATCH despite a list of new denials. This project's semver
table keys on features versus fixes: MINOR is "new handlers/features, config
options", PATCH is "bug fixes, security patches, docs". This bundle adds no
handler, feature or config option — every change is a bug or security fix, and
a security fix necessarily denies something that was previously allowed. The
release's own code review argued the bundle reads as MINOR under general
semver; that dissent was considered and recorded rather than dropped. If you
gate upgrades on the version component, read "Behaviour changes to expect"
below rather than the bump.
Upgrade Instructions
No special migration steps are required, and no configuration change is
needed: nothing was removed or renamed, and no previously-valid config fails.
Behaviour changes to expect. This is a bug-fix release, but several of
those fixes are security fixes, and a security fix necessarily denies
something it used to allow. Three RULES changed. They are stated as rules
because each covers an open-ended set — listing four examples of an unbounded
set is exactly the mistake the headline security fix in this release exists to
correct, so the examples below are illustrations, not the boundary.
1. The quoted-heredoc exemption became an allowlist. Previously any
receiver not on a list of known executors was granted the exemption; now only a
receiver on a list of 27 recognised DATA SINKS is (cat, tee, git, jq,
grep, checksum tools, …). Every other receiver withholds it — including
ssh, sed, awk, crontab, sqlite3, psql, mysql, env cat,
xargs echo, an assignment-prefixed command, and any receiver built by word
expansion ($SHELL).
Withholding does NOT deny the command — it scans the body that would otherwise
have been skipped. The denial only follows if that body also contains a
curl … | bash string. So an ordinary heredoc through an unlisted receiver is
unaffected; only one carrying the anti-pattern in its body is newly denied.
2. curl/wget piped to a VERSIONED interpreter is now caught.
curl … | python3 was allowed, along with /usr/bin/python3, python3.12,
ruby3 and perl5. Any interpreter name followed by a numeric version now
matches. Separately, a recognised sink whose OUTPUT is piped into an
interpreter — (cat <<'X' … X) | bash — no longer keeps the exemption.
3. Containment now resolves destinations it previously left unresolved.
Two shapes escaped the repository check and no longer do:
- A relative destination, for any destination-bearing construct
(curl -o,wget -O,mkdir -p,tar -cf,rsync) — e.g.
curl … -o ../../tmp/x.sh,mkdir -p ../shared/cache,
rsync -a src/ ../backup/. A relative path was treated as never-outside,
while the equivalent shell redirect was already denied. This is the one
most likely to appear in an ordinary workflow. - A directory destination, however spelled — a trailing slash
(cp README.md /tmp/,mv README.md /tmp/), the-t/--target-directory
form which has no trailing slash at all (cp -t /tmp/ x,
install -t /usr/local/bin/ x), and bare/.
Each denies a shape that could execute or exfiltrate, and each is covered by
a test. If one of these was load-bearing in your workflow, the handler's own
guidance names the supported alternative.
ccy supervisor users: restart the ccy session to get the paste framing.
The two halves of the /compact fix ship on different schedules, because the
supervisor is split into a long-lived PTY host and a hot-reloadable --worker
subprocess:
- The alternating Enter remedy is decided in the worker, so it goes live on
the next worker reload (~5s after the upgrade refreshes the file) with no
session restart. - The bracketed-paste framing is applied in
_perform_injection, which
runs in the host, and the host never hot-reloads. It takes effect only
when the ccy session itself is restarted.
Both behaviours are safe on their own, so an un-restarted session is not
broken — it simply keeps the pre-fix injection path plus the new Enter safety
net until you restart it.
Installation
New Installations
mkdir -p .claude
git clone -b v3.62.1 https://github.com/Edmonds-Commerce-Limited/claude-code-hooks-daemon.git .claude/hooks-daemon
bash .claude/hooks-daemon/scripts/install_version.sh "$PWD" "$PWD/.claude/hooks-daemon"Upgrading Existing Installations
cp .claude/hooks-daemon.yaml .claude/hooks-daemon.yaml.backup
git -C .claude/hooks-daemon fetch --tags
bash .claude/hooks-daemon/scripts/upgrade.sh --project-root "$PWD" "v3.62.1"
.claude/hooks-daemon/bin/hooks-daemon restartUse upgrade.sh (Layer 1), not upgrade_version.sh (Layer 2). Invoking
Layer 2 directly makes it check itself out half way through its own run, so
every step after that still comes from the release being replaced — a step the
new version ADDED does not run at all. That is the defect fixed in this
release, and it is what the v3.62.0 notes' own upgrade command triggered.
Testing
- Tests: 18,229 passing, 0 failed, 7 skipped, 95.2% coverage
- Type Safety: MyPy strict mode compliant
- Security: Bandit scan clean
- QA gate: 25/25 checks green
Release Review
This release's blocking code-review gate found six confirmed defects in the
bundle: one regression it introduced (a brace-group documentation heredoc was
newly denied), two ways the curl_pipe_shell exemption could still be granted
to a receiver that executes the body (three shell-escaping database clients on
the sink allowlist, and a sink whose output is piped into an interpreter), two
config-merge silent-loss defects, and one fabricated path in a containment
message. All six were fixed before tagging, with a failing test written first
for each. Fixing them surfaced a seventh, older bypass — curl … | python3 —
which is also fixed here.
A second review of the release documentation then found this file teaching the
Layer 2 upgrade command that the CHANGELOG entry says every document had been
fixed to stop teaching, plus several count and wording contradictions. Those
are corrected here too.
Contributors
- joseph
- Claude Opus 5 (Co-Authored-By across this release's commits)
Full Changelog
Compare: v3.62.0...v3.62.1