Skip to content

star lint shell finds scripts by their first line and checks each with shfmt, its own shell's -n, then shellcheck; every repository's shell gate runs it #1040

Open

Description

@David-Noble-at-work

star lint shell runs shfmt and shellcheck, and devlore-cli's CI runs it (.github/workflows/ci.yaml). It finds scripts by extension alone, .sh, .bash and .zsh, and the house's scripts carry none: run in David-Noble-at-work/personal's Home/noblefactor-ops.Unix/.local/bin, 22 scripts with no extension, it reports "No shell files found" and exits 0. It runs shellcheck without -P, so shellcheck cannot follow a script's source line to Declare-BashScript.

Each repository runs its own copy of .github/scripts/shell-lint.sh instead: shfmt, then shellcheck, on the files whose first line names sh or bash. devlore-cli's make shell-lint runs its copy while its CI runs star lint shell, so the two check different files. The copies' pattern matches #!/usr/bin/env bash but not #!/bin/bash or #!/bin/sh, so David-Noble-at-work/personal's Home/common.Unix/.config/git/hooks/pre-commit is checked by neither.

No gate parses a script with its own shell, and none reads zsh. 56 zsh files across the three repositories, the completions whose first line is #compdef and personal's Install-UnixUserConfiguration, are checked by nothing; all 56 pass zsh -n. shellcheck does not read zsh (on a completion it stops at SC2148, not knowing the shell), and shfmt parses a completion as bash unless told otherwise, and fails: "parameter expansion flags are a zsh feature". personal's Home/common.Unix/.zshenv is read by zsh but declares # shellcheck shell=bash.

Found 2026-10-10 on Danoble-MBP-A, against the three clones as they stood on disk, answering the owner's question on bash -n.

Ruled 2026-10-10

The owner: "Can we add bash -n to the static code analysis on bash scripts? I suggest that we run the check in this order: shfmt, bash -n, shellcheck." Then: "I suggest that it run in the context of star lint." Then the reasoning, as the owner drew it:

[ Raw Code ] 
     │
     ▼
 1. shfmt          ──► (Catches structural typos & formatting)
     │
     ▼
 2. bash -n        ──► (Validates engine-specific syntax compatibility)
     │
     ▼
 3. shellcheck     ──► (Performs deep logic, type, & safety analysis)
     │
     ▼
[ Deployable Script ]

"This documentation would be good to include with the star lint extensions for shell." And: "we should be smart enough to see whether we must run bash -n, sh -n, or zsh -n."

Asked whether to open this issue now as lane 94 of #916, to be worked after the config rollup: "open the issue as lane 94."

One limit, measured the same day: -n parses with the shell that runs the check, and parses only. macOS's /bin/bash 3.2 passes Start-Claude under -n, then refuses its ${resume_argument,,} at run time: "bad substitution".

Done when

  • star lint shell finds a script by its first line: a #! line naming sh, bash or zsh, #!/bin/sh and #!/bin/bash included; a # shellcheck shell= directive; or #compdef.
  • Each script is checked in the owner's order: shfmt, told the script's shell; then bash -n, sh -n or zsh -n, by the script's shell; then shellcheck, which a zsh file skips.
  • shellcheck is given the directory that holds Declare-BashScript, as the gates' -P gives it.
  • The extension's documentation carries the owner's picture.
  • devlore-cli's make shell-lint runs star lint shell; then David-Noble-at-work/personal's and NobleFactor/noblefactor-ops' CI run it in place of their own .github/scripts/shell-lint.sh, each in its own pull request.
  • A file whose directive names a shell other than the one that reads it, as .zshenv does, is checked as this issue's plan rules.

Plan and design documents

Kind Document
Plan Not yet opened

Epic: NobleFactor/noblefactor-ops#142 · Feature: NobleFactor/noblefactor-ops#266 · Schedule: #916, lane 94

Activity

  1. added
    choreMaintenance work on the repo: build, CI, tooling, process
    Epic:Ops:ProcessTooling axis: process work; never closes
    on Oct 11, 2026
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

    Epic:Ops:ProcessTooling axis: process work; never closeschoreMaintenance work on the repo: build, CI, tooling, process

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions