# ShellCheck adds new warnings with every release, so a floating install breaks green builds

> ShellCheck is a GPLv3 static analyser for bash and sh, written in Haskell and shipped as plain text, JSON, CheckStyle XML or GCC style warnings. Its own advice is to install a specific version rather than the newest one, because a release that introduces new warnings will fail a build that was green yesterday.

**koalaman/shellcheck** — ShellCheck, a static analysis tool for shell scripts. From your terminal Run shellcheck yourscript in your terminal for instant output, as seen above.

- Repository: https://github.com/koalaman/shellcheck
- Website: https://www.shellcheck.net
- Stars: 40,087 · Forks: 1,948
- Language: Haskell
- License: GPL-3.0
- Published: 2026-08-04 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/koalaman-shellcheck

## Three tiers of warning, written for three different readers

The stated goals of ShellCheck are grouped by how much shell you already know, and that grouping is the most useful thing in the README. The first tier is beginner syntax issues that cause a shell to give cryptic error messages. The second is intermediate semantic problems that make a shell behave strangely and counter-intuitively. The third is subtle caveats, corner cases and pitfalls that may cause an advanced user's otherwise working script to fail under future circumstances.

That third tier is the one that changes how you read the output. A finding there is not claiming your script is broken now. It is claiming your script depends on something that can change, which is a different conversation from the first tier and needs a different answer from you.

The gallery of bad code is organised the same way, with sections for quoting, conditionals, frequently misused commands, common beginner's mistakes, style, data and typing errors, robustness, portability and miscellaneous. Portability and style sit next to real bugs in the same list, so a project that treats every finding as a bug will end up arguing about the wrong things. There is also a section titled Ignoring issues, which is where the suppression mechanism lives.

## A new release can fail a build that passed yesterday

The README makes an admission that most linters do not, and then acts on it. It says it is a good idea to manually install a specific ShellCheck version regardless, and gives the reason: this avoids any surprise build breaks when a new version with new warnings is published.

That is a design consequence, not an accident. ShellCheck's third goal is to find pitfalls that appear under future circumstances, which means the rule set grows as people report cases. A fixed script checked at v0.9.0 can produce a clean exit at that version and a failing exit at the next one, with no change to the script and no change to your pipeline.

The release history shows the shape of that. v0.9.0 shipped on 2022-12-13, v0.10.0 on 2024-03-08 and v0.11.0 on 2025-08-04, and the last push to master was on 2026-09-21. Trunk Code Quality is singled out in the list of pre-installed services precisely because it lets you explicitly version your ShellCheck install, which is the whole of the advice in one line.

## One docker line offers a stable tag, an old version tag and a daily build

The container route is a single line, and it carries three different update policies.

```bash
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable myscript
```

The comment next to it says you can use :v0.4.7 for that version, or :latest for daily builds. So :stable moves when a release moves, :latest moves on every commit, and the versioned example in the file is v0.4.7 while the current release is v0.11.0. The example has not been refreshed.

That is not a criticism so much as a map of the choices. A CI job on :latest is the worst of the two failure modes at once, because it inherits both the new warnings and whatever the current commit contains. A job on :stable is defensible if you pin the digest, and a job on an explicit tag is the only one whose behaviour you can reason about. The working directory is bind mounted at /mnt, so the script path you pass is a path inside the container rather than on the host.

There is also koalaman/shellcheck-alpine for a larger Alpine base, which matters if the image needs a shell that can run the things you are checking.

## The package name changes case between distributions

Read the install list closely and the spelling is not consistent. Debian uses shellcheck, Arch uses shellcheck, Gentoo uses shellcheck, Homebrew and MacPorts and OpenBSD and Solus all use shellcheck. EPEL installs ShellCheck, Fedora installs ShellCheck, FreeBSD installs hs-ShellCheck, openSUSE installs ShellCheck, and both Cabal and Stack install ShellCheck.

On a case-insensitive filesystem this is invisible. On a case-sensitive one it is a command that fails with a not found error, and the fix is not obvious from the error text. FreeBSD is the stranger case, because hs-ShellFollow is not a typo: the Haskell package convention puts an hs- prefix on Haskell packages in that repository, so the name you search for is not the name on GitHub.

Two other details from the same section matter. The AUR carries a dependency free shellcheck-bin for Arch, which sidesteps the Haskell build entirely, and Cabal installs into ~/.cabal/bin while Stack installs into ~/.local/bin, so a successful build can land somewhere that is not on your PATH until you add it.

## Three plugins for named editors, then a compiler error format for the rest

The editor integration list is short because most of it is delegated. Vim has three named routes, through ALE, Neomake or Syntastic. Emacs has Flycheck and Flymake. Sublime has SublimeLinter. Pulsar Edit, the editor formerly known as Atom, has linter-shellcheck-pulsar. VSCode has vscode-shellcheck.

Then there is the catch-all, and it is the interesting one. For most other editors ShellCheck is used through GCC error compatibility, documented in the man page. Any tool that already parses compiler diagnostics can therefore consume ShellCheck output, which is why the format section and the editor section are the same section in practice.

That design has a consequence for teams. You do not need a first class ShellCheck integration in your editor to get value from it, and a team on an editor nobody has written a plugin for is not blocked. What you lose is per line squiggles, and what you keep is a linter that behaves the same in a terminal, in an editor and in a CI job, which is the property that matters when the findings have to be actionable by someone other than the person who typed the script.

## Exit codes are the contract, the richer formats need a parser

The README states that ShellCheck is mostly intended for interactive use and can easily be added to builds or test suites, and the reason is in the next sentence: it makes canonical use of exit codes, so you can add a shellcheck command as part of the process. The example given is a Makefile target and a Travis CI script block, both of which do the same thing.

```yaml
script:
  # Fail if any of these files have warnings
  - shellcheck myscripts/*.sh
```

Exit status is the only contract every service understands, and it is also the crudest one. It tells you that something failed, not which finding or where. For anything more precise there are four output formats: simple JSON, CheckStyle compatible XML, GCC compatible warnings, and human readable text with or without ANSI colours. The Integration wiki page is where the field level documentation lives.

The catch is that those fields belong to a version. A pipeline that parses JSON to allow or suppress individual findings is coupled to the ShellCheck release it wrote the parser against, which is the same coupling the version pinning advice is really about.

## The source is Haskell, and the tree carries a GHCi config and a dev entry point

Compiling ShellCheck means a Haskell toolchain, which is an unusual prerequisite for a tool that lints shell. The repository explains itself accordingly: ShellCheck.cabal and stack.yaml are the build definitions, .ghci is a GHCi configuration so the interpreter is set up for a read-eval loop, and there are two entry points, shellcheck.hs and shellcheck-dev.hs.

Then there is the release machinery at the top level. nextnumber and setgitversion exist to compute and stamp versions, quickrun and quicktest are shortcuts for running the thing and its tests, striptests for trimming test data, and there are directories for builders/, doc/, manpage, snap/ and test/, plus a Dockerfile.multi-arch and a CHANGELOG.md. The .github_deploy, .prepare_deploy and .multi_arch_docker entries are the release path.

For an adopter the practical summary is that you are far better off taking a binary release than building this. GPLv3 governs the tool you run, which matters if you embed it in a product, and the fastest route is the package manager or a pinned container tag rather than a Haskell build.

## Conclusion

ShellCheck fits a team that keeps shell scripts in version control and wants a linter in CI with a real exit code rather than a person reading warnings. It does not fit a pipeline that installs whatever is newest, because a release that adds warnings will fail builds that passed the day before. Pin a version first, as the README itself advises, and remember that only exit codes travel between services: the JSON and CheckStyle XML that give you per warning control live on the Integration wiki page and belong to the version you pinned.

## FAQ

### What is ShellCheck?

ShellCheck is a GPLv3 static analysis tool that gives warnings and suggestions for bash and sh shell scripts. Its goals are to point out beginner syntax issues that cause cryptic shell errors, intermediate semantic problems that make a shell behave strangely, and subtle pitfalls that can break an otherwise working script later. It outputs plain text, JSON, CheckStyle compatible XML or GCC compatible warnings.

### how to install shellcheck

The README's answer is your package manager, and it lists Cabal with cabal install ShellCheck into ~/.cabal/bin, Stack with stack install ShellCheck into ~/.local/bin, plus apt, pacman, emerge, dnf, brew, port, pkg_add, zypper, eopkg, conda-forge and snap. It also advises manually installing a specific version so a new release with new warnings cannot break your build.

### how to use shellcheck in vscode

The README points to the vscode-shellcheck extension for VSCode. It is one of five named integrations, alongside ALE, Neomake and Syntastic for Vim, Flycheck and Flymake for Emacs, SublimeLinter for Sublime, and linter-shellcheck-pulsar for Pulsar Edit. Most other editors use the GCC error compatibility format documented in the man page.

### Is ShellCheck a linter?

It is used as one. The README says ShellCheck is mostly intended for interactive use but can easily be added to builds or test suites, because it makes canonical use of exit codes, so a shellcheck command can be part of the process. The examples given are a Makefile target and a Travis CI script block.

### how to install shellcheck in windows

Three routes are listed: chocolatey with choco install shellcheck, winget with winget install --id koalaman.shellcheck, and scoop with scoop install shellcheck. Editors on Windows can also consume the GCC compatible output described in the man page if there is no first class plugin.

### how to install shellcheck on mac

On macOS the README lists brew install shellcheck for Homebrew and sudo port install shellcheck for MacPorts. Conda users can install from conda-forge, and the Snap Store route uses snap install --channel=edge shellcheck.

## Sources

- [Official documentation](https://www.shellcheck.net)
- [Official README](https://github.com/koalaman/shellcheck#readme)
- [Project repository](https://github.com/koalaman/shellcheck)
- [Release notes](https://github.com/koalaman/shellcheck/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/koalaman-shellcheck
