Skip to content

[Bug] lint:knip is red on develop: two chat-example tests spawn a cwd-relative script, and knip resolves it against the test's own directory #1641

Description

@pathosDev

What happened

bun run lint:knip (the step "knip — dead modules and undeclared imports" in
package-health.yml) exits 1 on local develop at 09f0751a:

Unresolved imports (2)
examples/chat/backend/main.ts  tests/unit/examples/ChatSampleUsageTexts.test.ts
examples/chat/backend/main.ts  tests/unit/examples/ChatSecretStartupOrder.test.ts

Both findings are false positives. Both tests arrived with the #791 work
(203f58bf, 09725e89, 2026-09-22). Each one passes the repository-relative literal
'examples/chat/backend/main.ts' as the first element of the args array to
spawn('bun', [...], { cwd: repositoryRoot }). The file exists, and bun finds it
because the spawn's cwd is the repository root.

origin/develop has neither test, so CI has not flagged this yet. The job goes
red on the next develop push.

This is not a regression from a dependency bump. bun.lock pinned knip 6.37.0
at both commits, and 6.37.0 and 6.38.0 report the same two lines.

Mechanism

The finding comes from knip's source-code script parser, not from the import
graph. Traced in knip 6.38.0's dist/:

  1. typescript/visitors/calls.js, handleCallExpression: in a file that
    imports node:child_process, a call to spawn, spawnSync, execFile or
    execFileSync goes through getSafeScriptFromArgs(executable, argsArray)
    (typescript/ast-nodes.js). That function joins the string-literal
    executable with every string-literal element of the args array into a
    shell script and skips non-literal elements. The two scripts here are
    bun examples/chat/backend/main.ts --help and
    bun examples/chat/backend/main.ts --port --http-port --seeds --data-dir.
    The parser is documented under "Source code" at
    https://knip.dev/features/script-parser, but that page does not say which
    directory it resolves against.
  2. graph/build.js passes those scripts to the binaries parser with
    cwd: dirname(filePath), which is the directory of the test file. knip
    never reads the cwd option of the call.
  3. binaries/resolvers/bun.js: the first positional argument is neither a bun
    subcommand nor a manifest script, so the resolver tries join(cwd, file),
    which is tests/unit/examples/examples/chat/backend/main.ts. That path does
    not exist, so the resolver re-parses the words as a command.
    binaries/fallback.js then turns a binary that contains / into an entry
    input.
  4. util/create-input-handler.js cannot resolve that entry relative to the
    test file's directory and reports it as unresolved.

Measured: when one literal is changed to the test-file-relative
'../../../examples/chat/backend/main.ts', that file's finding disappears and
the other one remains. knip resolves against the test's directory. With
cwd: repositoryRoot, though, bun would get the wrong path from that literal.

What did you expect

lint:knip exits 0 without a new ignore entry, and both tests keep spawning
the same file from the same working directory.

Reproduction

bun install --frozen-lockfile
bun run lint:knip        # exit 1, the two lines above

Environment

  • actor-ts at 09f0751a (local develop)
  • Bun 1.4.2, Windows 11
  • knip 6.38.0 (locked); 6.37.0 gives the same result

Fix sketch

Resolve the script the way the tests already mean it: as an absolute path,
join(repositoryRoot, 'examples', 'chat', 'backend', 'main.ts'), keeping
cwd: repositoryRoot. Every other spawn of a repository script in tests/
already does this, which is why none of them trips the parser:
CoverageGate.test.ts uses
SCRIPT = join(REPOSITORY_ROOT, 'scripts', 'coverage-gate.mjs'), and both
StressHarness* tests and DocSampleHarnessEndToEnd.test.ts do the same.
getSafeScriptFromArgs skips a non-literal element, so knip no longer sees a
path it resolves against the wrong directory. The tests themselves still
guarantee the file exists, because they fail if bun cannot load it.

Rejected alternatives:

  • An ignoreUnresolved entry in knip.jsonc. It silences the finding by name
    without removing its cause, and the next test that spawns an example with a
    relative literal would need another entry.
  • A test-file-relative literal. knip accepts it, but under this cwd it is the
    wrong path for bun.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginfrastructureCI / build / live-integration testspriority: mediumUseful, not urgent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions