Purpose
Thin check-centric setup per the uniform setup contract (docs/PLUGIN-PHILOSOPHY.md
"Setup is explicit and repeatable" in the marketplace repository): check inspects and
reports, apply resolves. This plugin owns no consumer-project configuration. Linting
rules come from the repository's own .shellcheckrc, formatting from its .editorconfig,
and the only tunable is the native userConfig toggle. Every prerequisite is a PATH
binary the plugin never bundles, and the plugin never installs system packages, so apply
is guidance-only with no write path. It never modifies the repository, user settings, or
the plugin cache.
Action routing: no argument or check runs the check; apply runs the check first, then
offers remediation guidance. Both are non-interactive. Never prompt when the action is given.
check (read-only)
The hook script (${CLAUDE_PLUGIN_ROOT}/hooks/bash-format.sh) is the single source of truth
for what it requires and how it resolves things.
Read it first. Probe what it actually does, don't recite this file. Then run each probe via
Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything.
The lint pass and the format pass are independent; report each separately.
When the plugin's toggle is disabled, every prerequisite absence downgrades from FAIL to
INFO. The hook exits through its enabled-gate before probing anything, so a deliberately
disabled plugin is not broken. Report the probes informationally and note that re-enabling
restores the FAIL semantics.
- Bash version. Check against the hook's documented floor (README Requirements),
noting any features the hook degrades without (for example telemetry's
EPOCHREALTIME,
a Bash 5.0+ builtin).
jq. command -v jq. FAIL if absent: the hook then skips with a visible
once-per-session notice instead of running either pass.
shellcheck (lint pass). command -v shellcheck. FAIL if absent: the lint pass
skips with a visible once-per-session notice.
shfmt (format pass). command -v shfmt. Its FAIL/INFO status depends on the
.editorconfig opt-in below, because the format pass runs only when the repo has opted
in:
- opted in AND
shfmt absent → FAIL: the format pass skips with a visible once-per-session
notice.
- not opted in → INFO regardless of
shfmt: the format pass stays quiet by design (the
repo chose not to format), so a missing shfmt is not a defect here.
.editorconfig shell opt-in. Mirror the hook's opt-in logic
(shell_editorconfig_opt_in / section_applies_to_shell), not merely "does an
.editorconfig exist". The opt-in is an EditorConfig section that names shell files:
a shell glob such as [*.sh], [*.bash], or [*.{sh,bash}] (including path-prefixed
forms like [**/*.sh]), discovered by walking up from the file's directory to the repo
root and stopping at a root = true config. A bare [*] catch-all does NOT count (most
repos only set line-ending / charset properties there). Path-only sections like
[scripts/**] do NOT count either. Report as INFO: whether a governing shell section
exists and therefore whether the format pass is active. If none exists, INFO-note the
consequence per the hook's logic: shell files are left unformatted rather than rewritten
to shfmt's built-in defaults.
.shellcheckrc. INFO: ShellCheck auto-discovers .shellcheckrc by walking up from
the file's directory. Report whether one exists; its absence is not a FAIL (ShellCheck
applies its own defaults).
- Hook toggle. Report the effective
bash_format_enabled value:
${user_config.bash_format_enabled} (unexpanded or empty means default true; any value
other than true disables the hook).
- Hook registration. INFO: confirm the plugin is enabled for this project
(
/plugin → Installed) rather than parsing settings files.
- Project scope. INFO: when
CLAUDE_PROJECT_DIR is set, the hook acts only
on shell files inside it (symlink-resolved membership guard in the shared hook
library, aware of Windows 8.3 short-name spellings of in-project paths, a
per-volume property; only volumes with 8.3 generation enabled produce them);
a .sh/.bash file written outside the project (temp/scratchpad
dirs) is silently skipped. No lint, no format, no notice. The OS temp tree
counts as outside even when it sits under CLAUDE_PROJECT_DIR, unless the
project root itself lives under temp. When
CLAUDE_PROJECT_DIR is unset (e.g. some headless -p sessions) the guard
is skipped and any existing edited file is processed. Report this so a green
check is not read as "every shell edit anywhere is covered".
When every probe passes, report the result with the scope caveat (item 9).
Never an unqualified "fully operational", which would imply out-of-project shell
edits are covered when they are deliberately skipped.
apply (idempotent)
Run check, then for each FAIL point at the resolution. This skill installs nothing:
- missing
shellcheck: install guidance from the README Requirements section
(the ShellCheck install guide); this
skill never installs system packages.
- missing
shfmt while the repo opts in: install guidance
(shfmt); this skill never installs system packages.
- missing
jq / Bash: platform install instructions from the README Requirements section.
- no shell
.editorconfig opt-in (and formatting is wanted): explain that adding a governing
shell section ([*.sh], [*.bash], or [*.{sh,bash}]) to an .editorconfig opts the
repo in. A bare [*] is not enough, but this skill does not write it. .editorconfig is
cross-cutting (it governs every editor and tool in the repo), so the choice and the edit
belong to the consumer.
- toggle off: reconfigure through Claude Code's native flow, per the marketplace's
plugin-reconfiguration convention
(https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md,
which owns the verified-version record): interactive
/plugin configure bash-format@<marketplace> any time, or headless
claude plugin install bash-format@<marketplace> -s <scope> --config bash_format_enabled=true
(repeatable per key) — against an already-installed plugin it prints already installed
and still writes the value. Do not uninstall to reconfigure: that drops this plugin's
entire stored pluginConfigs entry, resetting every option in the README's Options reference
to its manifest default. -s defaults to user; pass the scope claude plugin list reports
for this plugin, and run from that project's directory for a project/local scope, or the
write lands at a scope that does not load. This skill never writes user settings or
pluginConfigs. Afterwards rerun check in a fresh session — the rendered
${user_config.*} is injected at skill load and each hook receives its
CLAUDE_PLUGIN_OPTION_* from an environment fixed at session start, so a same-session
check still reports the OLD value; report the observed effective value, never an
unobserved change.
After pointing at a remediation, re-run the relevant check probe and report its actual
result. Never claim resolved on the reader's report that they installed something.
Re-running apply after everything passes changes nothing and reports "already configured".
What this skill does NOT do
- Run the linter or formatter. Editing any
.sh or .bash file exercises the hook
end-to-end.
- Write the plugin cache, Claude Code user settings, or
pluginConfigs. Nor the repository,
including .editorconfig / .shellcheckrc. Every prerequisite is a PATH binary or the
native toggle, so remediation is guidance only.
- Download or execute tools during
check beyond the read-only command -v presence probes.
Gotchas
ENAMETOOLONG when grepping the plugin cache. Installed plugins run from a
deeply nested, cache-isolated path. Piping a grep/rg over the long absolute
path to hooks/hook-utils.sh (or another bundled file) can spawn-fail with
ENAMETOOLONG on some hosts. Read the file directly (or cd into the plugin
hooks dir first and grep a short relative path) rather than passing the full
cache path on the command line.
check PASS ≠ every shell edit is covered. When CLAUDE_PROJECT_DIR is set
the hook is project-scoped (probe 9): shell files written outside it are silently
skipped, so a fully green check still does not cover out-of-project edits. (When
CLAUDE_PROJECT_DIR is unset the scoping does not apply. See probe 9.)
shfmt FAIL is opt-in-conditional. A missing shfmt is only a FAIL when an
.editorconfig section governs shell files; without that opt-in it is INFO, not
a defect. Resolve the .editorconfig opt-in state before calling shfmt a failure.
1---2name: setup-243description: Verify the bash-format hook's runtime prerequisites and configuration for this repository. Use when: 'set up bash-format', 'configure bash-format', 'is bash-format working', shell lint or formatting silently isn't happening, or the hook reported a missing prerequisite. Actions: check (read-only verification, default) | apply (resolve what check found). Re-runnable and safe.4---56## Purpose78Thin check-centric setup per the uniform setup contract (`docs/PLUGIN-PHILOSOPHY.md`9"Setup is explicit and repeatable" in the marketplace repository): `check` inspects and10reports, `apply` resolves. This plugin owns no consumer-project configuration. Linting11rules come from the repository's own `.shellcheckrc`, formatting from its `.editorconfig`,12and the only tunable is the native `userConfig` toggle. Every prerequisite is a `PATH`13binary the plugin never bundles, and the plugin never installs system packages, so `apply`14is guidance-only with **no write path**. It never modifies the repository, user settings, or15the plugin cache.1617Action routing: no argument or `check` runs the check; `apply` runs the check first, then18offers remediation guidance. Both are non-interactive. Never prompt when the action is given.1920## `check` (read-only)2122The hook script (`${CLAUDE_PLUGIN_ROOT}/hooks/bash-format.sh`) is the single source of truth23for what it requires and how it resolves things.2425**Read it first.** Probe what it actually does, don't recite this file. Then run each probe via26Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything.2728The lint pass and the format pass are independent; report each separately.2930When the plugin's toggle is disabled, every prerequisite absence downgrades from FAIL to31INFO. The hook exits through its enabled-gate before probing anything, so a deliberately32disabled plugin is not broken. Report the probes informationally and note that re-enabling33restores the FAIL semantics.34351. **Bash version.** Check against the hook's documented floor (README Requirements),36 noting any features the hook degrades without (for example telemetry's `EPOCHREALTIME`,37 a Bash 5.0+ builtin).382. **`jq`.** `command -v jq`. FAIL if absent: the hook then skips with a visible39 once-per-session notice instead of running either pass.403. **`shellcheck`** (lint pass). `command -v shellcheck`. FAIL if absent: the lint pass41 skips with a visible once-per-session notice.424. **`shfmt`** (format pass). `command -v shfmt`. Its FAIL/INFO status depends on the43 `.editorconfig` opt-in below, because the format pass runs **only when the repo has opted44 in**:45 - opted in AND `shfmt` absent → FAIL: the format pass skips with a visible once-per-session46 notice.47 - not opted in → INFO regardless of `shfmt`: the format pass stays quiet by design (the48 repo chose not to format), so a missing `shfmt` is not a defect here.495. **`.editorconfig` shell opt-in.** Mirror the hook's opt-in logic50 (`shell_editorconfig_opt_in` / `section_applies_to_shell`), not merely "does an51 `.editorconfig` exist". The opt-in is an EditorConfig **section that names shell files**:52 a shell glob such as `[*.sh]`, `[*.bash]`, or `[*.{sh,bash}]` (including path-prefixed53 forms like `[**/*.sh]`), discovered by walking up from the file's directory to the repo54 root and stopping at a `root = true` config. A bare `[*]` catch-all does NOT count (most55 repos only set line-ending / charset properties there). Path-only sections like56 `[scripts/**]` do NOT count either. Report as INFO: whether a governing shell section57 exists and therefore whether the format pass is active. If none exists, INFO-note the58 consequence per the hook's logic: shell files are left unformatted rather than rewritten59 to shfmt's built-in defaults.606. **`.shellcheckrc`.** INFO: ShellCheck auto-discovers `.shellcheckrc` by walking up from61 the file's directory. Report whether one exists; its absence is not a FAIL (ShellCheck62 applies its own defaults).637. **Hook toggle.** Report the effective `bash_format_enabled` value:64 `${user_config.bash_format_enabled}` (unexpanded or empty means default `true`; any value65 other than `true` disables the hook).668. **Hook registration.** INFO: confirm the plugin is enabled for this project67 (`/plugin` → Installed) rather than parsing settings files.689. **Project scope.** INFO: when `CLAUDE_PROJECT_DIR` is set, the hook acts only69 on shell files inside it (symlink-resolved membership guard in the shared hook70 library, aware of Windows 8.3 short-name spellings of in-project paths, a71 per-volume property; only volumes with 8.3 generation enabled produce them);72 a `.sh`/`.bash` file written *outside* the project (temp/scratchpad73 dirs) is silently skipped. No lint, no format, no notice. The OS temp tree74 counts as outside even when it sits under `CLAUDE_PROJECT_DIR`, unless the75 project root itself lives under temp. When76 `CLAUDE_PROJECT_DIR` is **unset** (e.g. some headless `-p` sessions) the guard77 is skipped and any existing edited file is processed. Report this so a green78 `check` is not read as "every shell edit anywhere is covered".7980When every probe passes, report the result **with the scope caveat** (item 9).81Never an unqualified "fully operational", which would imply out-of-project shell82edits are covered when they are deliberately skipped.8384## `apply` (idempotent)8586Run `check`, then for each FAIL point at the resolution. This skill installs nothing:8788- missing `shellcheck`: install guidance from the README Requirements section89 (the [ShellCheck install guide](https://github.com/koalaman/shellcheck#installing)); this90 skill never installs system packages.91- missing `shfmt` while the repo opts in: install guidance92 ([shfmt](https://github.com/mvdan/sh#shfmt)); this skill never installs system packages.93- missing `jq` / Bash: platform install instructions from the README Requirements section.94- no shell `.editorconfig` opt-in (and formatting is wanted): explain that adding a governing95 shell section (`[*.sh]`, `[*.bash]`, or `[*.{sh,bash}]`) to an `.editorconfig` opts the96 repo in. A bare `[*]` is not enough, but this skill does not write it. `.editorconfig` is97 cross-cutting (it governs every editor and tool in the repo), so the choice and the edit98 belong to the consumer.99- toggle off: reconfigure through Claude Code's native flow, per the marketplace's100 plugin-reconfiguration convention101 (<https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md>,102 which owns the verified-version record): interactive103 `/plugin configure bash-format@<marketplace>` any time, or headless104 `claude plugin install bash-format@<marketplace> -s <scope> --config bash_format_enabled=true`105 (repeatable per key) — against an already-installed plugin it prints `already installed`106 **and still writes the value**. Do **not** uninstall to reconfigure: that drops this plugin's107 entire stored `pluginConfigs` entry, resetting every option in the README's Options reference108 to its manifest default. `-s` defaults to `user`; pass the scope `claude plugin list` reports109 for this plugin, and run from that project's directory for a `project`/`local` scope, or the110 write lands at a scope that does not load. This skill never writes user settings or111 `pluginConfigs`. Afterwards rerun `check` in a **fresh session** — the rendered112 `${user_config.*}` is injected at skill load and each hook receives its113 `CLAUDE_PLUGIN_OPTION_*` from an environment fixed at session start, so a same-session114 `check` still reports the OLD value; report the observed effective value, never an115 unobserved change.116117After pointing at a remediation, re-run the relevant `check` probe and report its actual118result. Never claim resolved on the reader's report that they installed something.119120Re-running `apply` after everything passes changes nothing and reports "already configured".121122## What this skill does NOT do123124- Run the linter or formatter. Editing any `.sh` or `.bash` file exercises the hook125 end-to-end.126- Write the plugin cache, Claude Code user settings, or `pluginConfigs`. Nor the repository,127 including `.editorconfig` / `.shellcheckrc`. Every prerequisite is a `PATH` binary or the128 native toggle, so remediation is guidance only.129- Download or execute tools during `check` beyond the read-only `command -v` presence probes.130131## Gotchas132133- **`ENAMETOOLONG` when grepping the plugin cache.** Installed plugins run from a134 deeply nested, cache-isolated path. Piping a `grep`/`rg` over the long absolute135 path to `hooks/hook-utils.sh` (or another bundled file) can spawn-fail with136 `ENAMETOOLONG` on some hosts. Read the file directly (or `cd` into the plugin137 hooks dir first and grep a short relative path) rather than passing the full138 cache path on the command line.139- **`check` PASS ≠ every shell edit is covered.** When `CLAUDE_PROJECT_DIR` is set140 the hook is project-scoped (probe 9): shell files written outside it are silently141 skipped, so a fully green `check` still does not cover out-of-project edits. (When142 `CLAUDE_PROJECT_DIR` is unset the scoping does not apply. See probe 9.)143- **`shfmt` FAIL is opt-in-conditional.** A missing `shfmt` is only a FAIL when an144 `.editorconfig` section governs shell files; without that opt-in it is INFO, not145 a defect. Resolve the `.editorconfig` opt-in state before calling `shfmt` a failure.