v2.1.35 — Correction
bkit v2.1.35 — Correction
An outside contributor sent a one-file security hardening patch. Reproducing its
claim found that the vulnerability it reported was not reachable — and that two
real defects had been shipping in that same file since v2.1.12, one of which had
bkit telling users, in writing, something about Claude Code that is not true.
This release is what happens when you measure a patch instead of merging it.
Highlights
bkit told you its hooks might not work in a git worktree. They work.
Until this release, starting bkit inside a linked worktree printed:
git worktree detected — Claude Code hooks may not fire (issue #46808). Run bkit from the primary repository if hook-driven automation is required.
Nobody had ever measured that. We did, with a matched control — one live
claude -p --plugin-dir session inside a linked worktree, one in the primary
checkout of the same repository, both read back from bkit's own dispatch ledger:
| linked worktree | primary checkout | |
|---|---|---|
| hook events dispatched | SessionStart, InstructionsLoaded, UserPromptSubmit, Stop, SessionEnd |
identical set |
The hooks are not degraded in any observable way. The issue the advisory cited,
anthropics/claude-code#46808,
is closed as not planned, and it is about project-level
.claude/settings.json — a different configuration source from the plugin
hooks/hooks.json that bkit actually ships.
Detection stays, because a worktree really can be missing project-scope
.claude/ configuration when that directory is untracked or gitignored. What is
gone is the claim about bkit's own hooks, the citation of a declined issue as a
live defect, and the advice to leave your worktree.
A subdirectory of an ordinary checkout was reported as a worktree
git rev-parse --git-dir answers with an absolute path. --git-common-dir
answers relative to the current working directory. bkit resolved both against
the repository toplevel — the wrong base — so from repo/sub/deep it compared
/repo/.git against /repo/../../.git and concluded you were in a worktree.
Symlinked checkouts (/tmp → /private/tmp on macOS) failed the same way.
Detection now asks git for absolute paths directly and compares through
realpath. Verified across 8 topologies with zero mismatches, and both
superseded implementations are re-implemented inside the test suite as a negative
control — so the suite provably fails if either one returns.
Every child_process call in shipped code passes an argv array
PR #146 converted one call site. Seven remained, two of which interpolated
variables into a shell string: a remote name parsed out of your own git push,
and a GitHub handle inside a quoted search expression. Neither was exploitable,
and both are now argv — with a -- separator so a leading-dash remote name
cannot be read as a flag.
This was already the project's policy; it had just never been enforced. A
contract test now enforces it mechanically, and Claude Code version detection
went from three implementations (two of them shelling out) to one.
What changes for you
| Before | After |
|---|---|
| Starting bkit from a subdirectory printed a worktree warning and left a stray flag file behind | No warning — a plain checkout is recognized as one |
| Working in a git worktree told you hooks "may not fire" and to go back to the main checkout | You're told what is actually at risk — project-scope .claude/ config — and that bkit's own hooks are unaffected, with the Claude Code version the claim was measured against recorded in the flag file |
| The warning cited an issue that had been declined, as though it were live | The advisory cites only what currently reproduces |
| Nothing else visible | Nothing else changes. No commands, skills, agents, or state formats moved. |
Upgrading is a drop-in: claude plugin update bkit. There are no migrations and
no configuration changes.
For maintainers
Three defects in this release were found by running things, not reading them, and
all three are the same shape — a claim nobody re-measured:
- The worktree advisory asserted a behavior of Claude Code that had never
been tested and had stopped being true. - A quality gate could fail because bkit was running.
SB-011compared two
reads of the developer's live.bkit/state/taken ~80 lines apart, and a bkit
session in the same repository rewritestrust-profile.jsonbetween them.
Observed ascontrol: 38, engine: 50, passing on the next run. Both readings
now come from one child process pinned to an emptyCLAUDE_PROJECT_DIR—
deterministic on a fresh clone, on a dogfooding machine, and in CI. - The test runner listed four files it could not find, and counted them as
skips. v2.1.16's stale-test cleanup deleted the files and left the manifest
entries. For 19 releases the generated report printed them under Failures
while the verdict counted them as skips — a report that listed failures it did
not count. Fixed, and a contract test now fails if the manifest and the
filesystem ever disagree again.
WorktreeCreate / WorktreeRemove registration stays deferred on the reasoning
recorded in v2.1.33 (ENH-396/418): confirmed supported by Claude Code, deferred
for the hook-count cascade. This release removes an incorrect claim about
worktrees; it does not add worktree lifecycle management.
ENH: 424–431.
Credits
@anupamme — PR #146.
A one-file execSync → execFileSync hardening patch that, on reproduction,
turned into this release. The migration is theirs and now covers the whole
repository.
The semgrep finding behind it (javascript.lang.security.detect-child-process,
HIGH) is a true description of the pattern and a false description of the risk at
that call site — all three callers passed module-internal literals. We kept and
extended the migration anyway, because removing the shell means a future caller
cannot reintroduce the primitive. That is the useful half of a scanner finding
that did not reproduce.