Skip to content

chunk init writes npm test as fallback for unrecognized toolchains; lacks requirements.txt case #322

@felixshiftellecon

Description

@felixshiftellecon

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:

pytest==8.3.3

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:

  1. 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.

  2. 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:

  1. 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).
  2. Optionally add Ruby (Gemfilebundle exec rspec or rake test), Maven (pom.xmlmvn test), and Gradle (build.gradle*./gradlew test) cases to round out the deterministic path.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions