You read Bun's linter docs, ran the first command on the page, and got error: Script not found "lint". This walks through the three causes that print that one line, the single command that tells them apart, and ends with the linter actually running, pinned to the same build locally and in CI, with your ESLint config still honoured.

Short version. On Bun 1.4.4, error: Script not found "lint" almost always means this build has no linter and bun lint was handled by the script runner as bun run lint. Confirm with bun --help and bun fmt --help, then install a canary build to get the linter. An unparseable package.json and a lint script of your own both print the identical string, and each has its own one-command test.

The docs page at https://bun.com/docs/lint opens with "Bun ships with a fast, built-in linter. There's nothing to install and nothing to configure," carries no version note, and the site's install banner says v1.4.4. The v1.4.4 release post (https://bun.com/blog/bun-v1.4.4), dated 22 October 2026 and signed by Jarred Sumner, announces bun fmt as "an incredibly fast, Prettier-compatible code formatter" and never mentions bun lint. The linter was merged on 12 October 2026 in https://github.com/oven-sh/bun/pull/44805, titled "Add bun lint and bun format", ten days before 1.4.4 shipped without it. So the docs describe a command the newest stable binary does not answer to, and https://github.com/oven-sh/bun/issues/47358 is still open with no maintainer reply. An error string with more than one cause needs a decision tree rather than a fix list; npm ci's are in sync has four of them.

Prerequisites

  • A project with a package.json and a bun.lock you can regenerate.
  • Bun installed. Check with bun --version. This is written against 1.4.4, released 22 October 2026, the newest stable on both bun.com/blog and the GitHub releases page as of 9 November 2026.
  • Willingness to run a canary build. Bun's install docs (https://bun.com/docs/installation) describe canary as "an (untested) canary build on every commit to main", so plan it as a tool upgrade with a rollback rather than a production runtime bump.
  • git, if you want bun lint --staged or --changed later.
  • Optional: a repo using oven-sh/setup-bun in GitHub Actions.

Step-by-step

1. Rule out a broken package.json before you blame the linter

bun install

If package.json does not parse, Bun's script-runner exit path prints only error: Script not found "..." and swallows the JSON error, while bun install prints the real thing with a caret and a position. That is the behaviour reported in https://github.com/oven-sh/bun/issues/42992:

1 | {"scripts":{"hello":"echo ran"},,}
                                    ^
error: Expected string but found ","
    at /tmp/t/package.json:1:33

The issue was closed as not planned on 8 October 2026, so the swallowing stands. A trailing comma in a file you edited this morning is the cheapest explanation for your error, and one command eliminates it. Parsers that report the wrong failure are a recurring tax on this kind of triage, as with a state file padded with NUL bytes.

2. Ask the binary what it can do, not what version it claims

bun --help
bun fmt --help
bun lint --help

On 1.4.4 the third command prints error: Script not found "lint" as well, which the reporter in #47358 shows verbatim alongside bun lint --explain prefer-const failing identically. The flags never reach a parser because there is nothing to parse them. That report also notes lint is absent from the bun --help command list while bun fmt works.

Treat this as a capability probe, and trust it over a version string. On a patch day last month I checked a Node Brotli CVE on my own box by reading the decoder's own errno=-9 instead of node -v, because distro and vendored builds make the release number wrong in both directions. bun --version saying 1.4.4 tells you nothing about whether this binary has a linter; bun --help does.

3. Install a build that has the linter, keeping a way back

bun upgrade --canary
bun --help | grep -w lint

Both bun upgrade --canary and bun upgrade --stable are documented on https://bun.com/docs/installation, the latter under the comment "Switch back to stable". The canary route is where the reporter in #47358 landed: "I used bun upgrade --canary and it's working now," with a second reporter on macOS and a third on Windows hitting the stable wall first. If the grep still prints nothing, stop and comment on #47358 rather than editing config.

4. Write the lint config instead of inheriting one by accident

bun lint --init

That writes a [lint] section into bunfig.toml, with rules under [lint.rules]:

[lint]
ignorePatterns = ["dist/**", "coverage/**"]

[lint.rules]
"no-console" = "warn"

If you already have eslint.config.js, .eslintrc or .oxlintrc.json, the docs say bun lint reads it and "reports what that tool reports", and that with an eslint.config.* file present the ignores come from ESLint's ignores field rather than bunfig.toml. JSON and Markdown rules still need an ESLint config. Keep your config and run bun lint --print-config to see what was resolved. The docs list that flag without describing its output, so read the first run as discovery.

5. Does a lint script override the built-in linter?

bun lint --no-script

From the docs: "If package.json has a lint script, bun lint runs that script. Inside the script, bun lint is the linter." That rule is why the error is invisible to half a team. A repo with "lint": "eslint ." never sees Script not found on 1.4.4 because the script is found, so ESLint runs and nobody notices the built-in was never involved. A workspace root with no lint script shows the error immediately, matching the reproduction in #47358 ("Run bun lint in a workspace"). Rename the old script to eslint:legacy when you want the built-in on the default path, and pass --no-script until you do.

6. Pin the same build in CI

- uses: oven-sh/setup-bun@v2
  with:
    bun-version: canary
- run: bun lint --no-script --format github --max-warnings 0

The documented default for bun-version in https://github.com/oven-sh/setup-bun is "Version from package.json, or latest", so a pipeline you do not touch resolves to stable and loses the linter while your laptop has it. Unpinned toolchains move under you on someone else's schedule, which is how node20 ended up running on Node 24. --format github is one of the documented formatters (pretty, stylish, json, unix, github, junit, checkstyle, gitlab, sarif, agent) and turns findings into annotations on the diff.

bun lint prints Script not foundDoes bun install print a JSON parse error?Cause 1: package.json does not parseDoes bun --help list lint?Cause 2: this build has no linter, 1.4.4 stableIs there a lint script in package.json?Cause 3: your script shadows the built-in, add --no-scriptUnreported shape: comment on issue 47358yesnonoyesyesno

Verify it works

bun lint --explain prefer-const
bun lint --rules
bun lint --no-script; echo "exit=$?"

The first command printed the error string before step 3 and prints rule documentation after it, which makes it the cleanest before and after on one line. --rules lists every built-in rule; PR #44805's description claims "1,021 rules built in, type-aware rules included", which is Bun's figure rather than one I measured, so count the output and trust your count. On exit codes the docs are explicit: 1 for errors, 0 when there are only warnings, and 2 when package.json cannot be parsed. A gate written as if [ $? -eq 1 ] therefore treats a broken manifest as a pass, the same shape of hole as drift exiting 0 under -minimal-refresh. Gate on -ne 0.

Common pitfalls

  • Triaging with --help on the subcommand. On a build without the linter, bun lint --help prints the same Script not found line quoted in #47358, so it proves nothing. The pair that discriminates is bun --help and bun fmt --help.
  • Package-manager-installed Bun. bun upgrade manages Bun's own install. If bun came from Homebrew, mise or a distro package, run type bun first and update through that tool, or you finish with two binaries and an unclear PATH.
  • Assuming --type-aware is on. It is a separate documented flag, as is --type-check (typeCheck = true in bunfig.toml). A green run without either has type-checked nothing.
  • Deleting your ESLint config on day one. The built-in reads it today, and its reporting is only as trustworthy as your first side-by-side run. Keep both for a week.
  • What I could not confirm. Nothing in the v1.4.4 post, PR #44805 or the release page states why stable omits the linter, and no maintainer has answered #47358, so "canary-only gate" is inference from behaviour rather than a documented decision. Two checks settle it when the next stable lands: bun upgrade --stable && bun --help | grep -w lint on the new binary, and gh api repos/oven-sh/bun/issues/47358 --jq '.state, .comments' to see whether a maintainer has finally answered.

Wrap-up

You now know which of the three causes produced error: Script not found "lint", you have a build whose --help lists the command, a [lint] section you wrote on purpose, and a CI job pinned to that build behind an exit-code gate that catches a broken manifest. Re-run bun --help | grep -w lint on the first stable release after 1.4.4, and drop bun-version: canary from the workflow the day the command appears there.

FAQ

Is the canary linter safe to run in CI? Bun's install docs call canary "an (untested) canary build on every commit to main", so scope it to the lint job with its own setup-bun step and leave build and runtime jobs on a pinned stable version.

How do I go back to stable? bun upgrade --stable, documented at https://bun.com/docs/installation. Check type bun first if a package manager installed it.

What happens to my existing eslint.config.js? bun lint reads it and reports what ESLint reports, and the ignore list then comes from ESLint's ignores rather than bunfig.toml. JSON and Markdown rules still require an ESLint config.

Why is exit code 2 not a lint failure? It means package.json could not be parsed. Gating on -ne 0 covers both that and real lint errors.

What I checked, and when