Summary
chunk init writes "run": "npm test" to .chunk/config.json and wires npm test into the Claude Code PreToolUse hook when run in a project whose toolchain is not in the deterministic detection list in internal/validate/setup.go and when no ANTHROPIC_API_KEY is configured. In practice this fires for Python projects that use requirements.txt (rather than pyproject.toml), for Ruby (Gemfile), for Maven/Gradle, and for any other stack not enumerated in DetectCommands.
The same project is correctly identified by chunk sidecar env, which uses the detector in envbuilder/. The two detection paths disagree.
Environment
chunk version: 0.7.44 (Homebrew tap CircleCI-Public/circleci/chunk)
- OS: macOS 25.4.0 (Apple Silicon)
ANTHROPIC_API_KEY: unset (the user-facing docs describe this as optional for init)
Steps to reproduce
In an empty directory, create the following four files. No package.json, no pyproject.toml, no node_modules, no JavaScript lockfile.
calculator.py:
def add(a: int, b: int) -> int:
return a + b
test_calculator.py:
from calculator import add
def test_add():
assert add(1, 2) == 3
requirements.txt:
Initialize git so chunk init can resolve VCS metadata:
git init -q
git add -A
git -c user.email=test@test -c user.name=test commit -q -m initial
git remote add origin https://github.com/example/repro.git
Then run, with ANTHROPIC_API_KEY unset:
chunk init --skip-completions --skip-skills </dev/null
Observed behavior
Detected repository: example/repro
Detected command: test (npm test)
✓ Wrote .chunk/config.json
✓ Updated .gitignore with sidecar tracking patterns
✓ Wrote .claude/settings.json
✓ Project initialized
.chunk/config.json:
{
"commands": [
{ "name": "test", "run": "npm test", "role": "gate" }
],
"vcs": { "org": "example", "repo": "repro" }
}
.claude/settings.json PreToolUse hook (truncated):
{
"matcher": "Bash(git commit*)",
"hooks": [
{
"type": "command",
"command": "cd ${CLAUDE_PROJECT_DIR:-.} && npm test",
"timeout": 60
}
]
}
Every subsequent Claude Code commit in this repository invokes npm test, which fails because npm is not installed and no package.json exists.
Cross-check with chunk sidecar env
Running chunk sidecar env in the same directory returns the correct stack and commands:
{
"stack": "python",
"setup": [
{ "name": "install", "command": "pip install -r requirements.txt" },
{ "name": "test", "command": "pytest" }
],
"image": "cimg/python",
"image_version": "3.14.3"
}
So the binary already contains a working detector for this case; chunk init does not use it.
Root cause
In internal/validate/setup.go:
const defaultTestCommand = "npm test"
DetectCommands switches on a fixed set of root-level files:
Taskfile.yml / Taskfile.yaml
Makefile
go.mod
Cargo.toml
pyproject.toml
package.json
If none match and Claude is unavailable, the fallback is:
if claude == nil {
return []config.Command{{Name: "test", Run: defaultTestCommand, Role: config.RoleGate}}, nil
}
Two issues compound here:
-
requirements.txt, setup.py, and Pipfile are not in the switch. Many Python projects (and most introductory tutorials) do not use pyproject.toml. The signals are recognized further down in gatherRepoContext, which adds them to the Claude prompt, but the deterministic path never consults them. The same gap exists for Ruby (Gemfile), Java (pom.xml, build.gradle*), and Clojure (project.clj/deps.edn) — all listed in gatherRepoContext but absent from the switch.
-
defaultTestCommand = "npm test" is a misleading fallback. When the toolchain cannot be determined, the safer behaviors are (a) emit no commands and surface a warning, or (b) emit an explicit no-op like echo "no test command configured" && false. Writing npm test for a Python or Ruby project produces a hook that is guaranteed to fail on every commit.
Suggested fix
In internal/validate/setup.go:
- Add a Python case to the switch that triggers on
requirements.txt, requirements-dev.txt, requirements-test.txt, setup.py, or Pipfile, emitting pytest as the test command (consistent with the existing pyproject.toml branch).
- Optionally add Ruby (
Gemfile → bundle exec rspec or rake test), Maven (pom.xml → mvn test), and Gradle (build.gradle* → ./gradlew test) cases to round out the deterministic path.
- Change the
claude == nil fallback from npm test to either (a) returning an empty command list with a warning logged, or (b) consulting the envbuilder/ detector that chunk sidecar env already uses successfully.
Impact
chunk init is the first command in the documented quick-start. For any non-Node project that does not happen to have pyproject.toml, Cargo.toml, go.mod, a Makefile, or a Taskfile, the resulting .chunk/config.json and Claude PreToolUse hook are misconfigured, and the user has to manually edit both files before Claude-driven commits will work.
Summary
chunk initwrites"run": "npm test"to.chunk/config.jsonand wiresnpm testinto the Claude CodePreToolUsehook when run in a project whose toolchain is not in the deterministic detection list ininternal/validate/setup.goand when noANTHROPIC_API_KEYis configured. In practice this fires for Python projects that userequirements.txt(rather thanpyproject.toml), for Ruby (Gemfile), for Maven/Gradle, and for any other stack not enumerated inDetectCommands.The same project is correctly identified by
chunk sidecar env, which uses the detector inenvbuilder/. The two detection paths disagree.Environment
chunkversion: 0.7.44 (Homebrew tapCircleCI-Public/circleci/chunk)ANTHROPIC_API_KEY: unset (the user-facing docs describe this as optional forinit)Steps to reproduce
In an empty directory, create the following four files. No
package.json, nopyproject.toml, nonode_modules, no JavaScript lockfile.calculator.py:test_calculator.py:requirements.txt:Initialize git so
chunk initcan resolve VCS metadata:Then run, with
ANTHROPIC_API_KEYunset:chunk init --skip-completions --skip-skills </dev/nullObserved behavior
.chunk/config.json:{ "commands": [ { "name": "test", "run": "npm test", "role": "gate" } ], "vcs": { "org": "example", "repo": "repro" } }.claude/settings.jsonPreToolUsehook (truncated):{ "matcher": "Bash(git commit*)", "hooks": [ { "type": "command", "command": "cd ${CLAUDE_PROJECT_DIR:-.} && npm test", "timeout": 60 } ] }Every subsequent Claude Code commit in this repository invokes
npm test, which fails becausenpmis not installed and nopackage.jsonexists.Cross-check with
chunk sidecar envRunning
chunk sidecar envin the same directory returns the correct stack and commands:{ "stack": "python", "setup": [ { "name": "install", "command": "pip install -r requirements.txt" }, { "name": "test", "command": "pytest" } ], "image": "cimg/python", "image_version": "3.14.3" }So the binary already contains a working detector for this case;
chunk initdoes not use it.Root cause
In
internal/validate/setup.go:DetectCommandsswitches on a fixed set of root-level files:Taskfile.yml/Taskfile.yamlMakefilego.modCargo.tomlpyproject.tomlpackage.jsonIf none match and Claude is unavailable, the fallback is:
Two issues compound here:
requirements.txt,setup.py, andPipfileare not in the switch. Many Python projects (and most introductory tutorials) do not usepyproject.toml. The signals are recognized further down ingatherRepoContext, which adds them to the Claude prompt, but the deterministic path never consults them. The same gap exists for Ruby (Gemfile), Java (pom.xml,build.gradle*), and Clojure (project.clj/deps.edn) — all listed ingatherRepoContextbut absent from the switch.defaultTestCommand = "npm test"is a misleading fallback. When the toolchain cannot be determined, the safer behaviors are (a) emit no commands and surface a warning, or (b) emit an explicit no-op likeecho "no test command configured" && false. Writingnpm testfor a Python or Ruby project produces a hook that is guaranteed to fail on every commit.Suggested fix
In
internal/validate/setup.go:requirements.txt,requirements-dev.txt,requirements-test.txt,setup.py, orPipfile, emittingpytestas the test command (consistent with the existingpyproject.tomlbranch).Gemfile→bundle exec rspecorrake test), Maven (pom.xml→mvn test), and Gradle (build.gradle*→./gradlew test) cases to round out the deterministic path.claude == nilfallback fromnpm testto either (a) returning an empty command list with a warning logged, or (b) consulting theenvbuilder/detector thatchunk sidecar envalready uses successfully.Impact
chunk initis the first command in the documented quick-start. For any non-Node project that does not happen to havepyproject.toml,Cargo.toml,go.mod, aMakefile, or aTaskfile, the resulting.chunk/config.jsonand ClaudePreToolUsehook are misconfigured, and the user has to manually edit both files before Claude-driven commits will work.