code-quality-plugin/skills/code-hidden-failures/SKILL.md
Scan for hidden failures: swallowed errors (empty catch, || true, 2>/dev/null) and silent degradation (success on zero results). Use when failures vanish or success masks empty output.
npx skillsauth add laurigates/claude-plugins code-hidden-failuresInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Detect code that fails without saying so. Two tracks:
| Track | Failure shape | Example |
|-------|---------------|---------|
| errors | Syntactic — an error signal is discarded | catch (e) {}, \|\| true, 2>/dev/null, floating promise, _ = err |
| degradation | Logical — an operation "succeeds" with empty/useless output because a precondition was silently unmet | success toast on count === 0, if (!apiKey) return [], a 1-of-3 detector run with no indication |
The two were previously separate skills (code-error-swallowing +
code-silent-degradation); they are the same user intent — "the work
reported success but nothing real happened" — so they live in one scanner
with a --track selector.
| Use this skill when... | Use another skill instead when... |
|------------------------|-----------------------------------|
| Scripts/CI report success but real work failed | /code:antipatterns — broad multi-category scan |
| \|\| true, 2>/dev/null, empty catch {}, except: pass suspected (errors track) | /code:review — prose code review |
| A feature reports success but produces nothing (degradation track) | /code:lint — a linter already flags the issue |
| Scans return 0 results / success banners on empty outcomes | /code:dead-code — you suspect code never runs |
| You need severity classification + a surfacing recommendation | — |
$ARGUMENTS (defaults to current directory)find . -maxdepth 2 \( -name '*.sh' -o -name '*.bash' -o -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.go' -o -name '*.rs' \) -type f -not -path './node_modules/*' -not -path './.git/*'find . -maxdepth 2 \( -name 'index.html' -o -name 'vite.config.*' -o -name 'next.config.*' \) -type ffind . -maxdepth 2 \( -name 'bin' -type d -o -name 'Makefile' -o -name 'justfile' \)find . -maxdepth 2 \( -name 'Dockerfile' -o -name '*.service' -o -name 'pyproject.toml' \) -type ffind . -maxdepth 2 \( -name '.env*' -o -name 'config.*' -o -name 'settings.*' \) -type ffind . -path '*/.github/workflows/*' -maxdepth 3 -name '*.yml' -type fParse from $ARGUMENTS:
PATH: directory or file to scan (defaults to .)--track <errors|degradation|both>: which track to run (default both)--lang <shell|js|py|go|rust|auto>: errors track — restrict to one language (default auto)--severity <low|med|high>: minimum severity to report (default med)--emit-patch: errors track — emit a unified-diff patch on stdout (no in-place mutation; apply with git apply)--fix: degradation track — apply recommended fixes in place (precondition checks, status indicators, distinguishing copy)--emit-patch and --fix are mutually exclusive — the errors track reviews
its surfacing copy via a patch, the degradation track applies structural
fixes directly.
Run the selected track(s). Default both: run errors first, then degradation,
then a combined summary.
Run when --track is errors or both.
From the context commands above, determine which language matchers to run. For the app-context matrix (signals → surfacing channel), load REFERENCE-surfacing.md.
The AST-shaped swallowed-error patterns (js/ts, python, go, rust) live as an
ast-grep rule project in rules/ (one *.yml per pattern under
rules/lib/, each with a valid/invalid fixture in rules/tests/). Run the whole
catalog in one deterministic pass — do not re-type sg -p '…' per language:
ast-grep scan -c ${CLAUDE_SKILL_DIR}/rules/sgconfig.yml --json=compact <path>
Each finding carries ruleId, file, range (line/column), message, and the
matched text — the raw finding set for Steps A3–A6.
The shell / bash track stays grep-based — || true, 2>/dev/null in context,
and xtrace suppression are line-shaped, not AST-shaped:
bash ${CLAUDE_SKILL_DIR}/scripts/scan-shell.sh <path>
| Track | Tool | Reference (linked from rule files) |
|-------|------|------------------------------------|
| JS / TS, Python, Go, Rust | ast-grep scan -c rules/sgconfig.yml (one pass) | REFERENCE-js.md, REFERENCE-python.md, REFERENCE-go.md, REFERENCE-rust.md |
| Shell / bash | scripts/scan-shell.sh (grep-based) | REFERENCE-shell.md |
Graceful degradation — if ast-grep (packaged as ast-grep or sg) is not
installed, fall back to the per-pattern flow: read the REFERENCE-{js,python,go, rust}.md files (each links its patterns to the rule .yml) and run the
individual sg -p '<pattern>' --lang <lang> commands by hand. Prefer the repo's
own errcheck/staticcheck (Go) or cargo clippy (Rust) when configured — the
rule project surfaces what those linters would catch, it does not replace them.
For every finding, capture: file:line, matched snippet, surrounding function
name if discoverable.
For every raw finding, assign Low / Medium / High:
| Severity | Criteria | Examples |
|----------|----------|----------|
| Low | Matches a documented allowlist entry or the catch block has a log call + rethrow. | Frontmatter extraction \|\| true (see .claude/rules/shell-scripting.md lines 135–162); except FileNotFoundError: pass around an optional cache. |
| Medium | Error suppressed with no log, no fallback value, no surfacing, on a recoverable operation. | catch (e) {} around a UI-layer fetch; \|\| true after make lint. |
| High | Suppression around a required operation: data writes, auth, secret handling, config loading, release builds, push/deploy. | npm publish 2>/dev/null \|\| true; except: pass around a DB commit; _ = os.Remove(tmpPath) on a path the caller assumed was cleaned. |
Apply the per-language allowlist rules from each REFERENCE-*.md before
assigning Low.
For each Medium/High finding, consult REFERENCE-surfacing.md to pick the
channel appropriate to the detected app context — do not recommend a
uniform "log and rethrow":
| App context | Recommended channel |
|-------------|---------------------|
| CLI / shell | echo "warn: ..." >&2 + non-zero exit on High |
| Web frontend | console.error + user-facing toast/banner with sanitized copy |
| Web backend / daemon | Structured log (error ID) + generic 5xx + opaque user message |
| Library | Re-raise / return Result / propagate — do not surface to user |
| CI / build script | echo "::error::..." (GitHub) or stderr + non-zero exit |
Every suggested replacement (report and --emit-patch) MUST pass through
the redaction rules in REFERENCE-surfacing.md §Privacy:
*TOKEN*, *KEY*, *SECRET*, *PASSWORD*, GH_*, ANTHROPIC_*, AWS_*) → [REDACTED].$HOME, /Users/…, /home/…) → ~.set -x / xtrace output.For web frontend, split: verbose detail → console.error; short sanitized
copy → UI channel.
--emit-patch)Generate a unified diff to stdout (not written to files) that covers only
Medium/High findings, applies the app-context-appropriate channel, runs every
inserted string through the Step A5 redaction, and adds a
# TODO(hidden-failures): review wording comment next to each generated
user-facing message. Remind the user: git apply <patchfile>.
Run when --track is degradation or both. Detection patterns, severity
guide, and fixes live in REFERENCE-degradation.md.
Glob **/*.{ts,tsx,js,jsx,py,go,rs} in the target path, excluding
node_modules, dist, build, .git, vendor, __pycache__.
Match the five pattern categories from
REFERENCE-degradation.md: silent config skip,
success on zero results, silent step skipping, missing precondition
validation, hidden degraded mode. For each finding capture file:line, which
pattern, what the user experiences, and the preconditions the code needs.
High = success messaging when nothing worked (patterns 2, 3); Medium = functionality silently disabled by config/env (patterns 1, 5); Low = missing upfront validation (pattern 4).
--fix)Apply the per-pattern fixes from
REFERENCE-degradation.md § Recommended Fixes in
place, then list every change with file:line references.
Group by severity descending; omit Low unless --severity low. Tag each row
with its track.
Hidden-Failure Scan: <path> (track: both)
Detected app context: <cli|frontend|backend|library|daemon|ci>
| Track | Severity | File:Line | Pattern | Recommended action |
|-------------|----------|-----------------|-----------------------------|----------------------------------|
| errors | High | release.sh:42 | `npm publish ... \|\| true` | stderr + exit 1 |
| degradation | High | scan.ts:88 | success on zero results | distinguish "none" vs "skipped" |
| errors | Medium | api/fetch.ts:17 | empty catch | console.error + toast (sanitized)|
Totals: errors(high=N med=N low=N) degradation(high=N med=N low=N) across M files
| Context | Command |
|---------|---------|
| Default scan (both tracks) | /code:hidden-failures . |
| Errors only, shell, high severity | /code:hidden-failures . --track errors --lang shell --severity high |
| Degradation only, with fixes | /code:hidden-failures src/ --track degradation --fix |
| Review-ready error patch | /code:hidden-failures src/ --track errors --emit-patch > /tmp/fix.patch |
rules/ — the executable ast-grep catalog for the errors track (sgconfig.yml + rules/lib/*.yml + rules/tests/*-test.yml); run ast-grep test -c rules/sgconfig.yml --skip-snapshot-tests to verify every rule against its fixtures/code:antipatterns — delegates here for the error-swallowing category/code:review — prose code review.claude/rules/shell-scripting.md — canonical allowlist for shell \|\| true / 2>/dev/nullREFERENCE-surfacing.md — app-context → channel matrix and privacy rules (errors track)REFERENCE-degradation.md — the five degradation patterns, severities, and fixes (degradation track)/configure:sentry, /configure:feature-flags — surfacing/monitoring infrastructuredevelopment
Debug HTTP APIs: trace requests, inspect headers. Use when a request fails: check status first.
documentation
Render architecture diagrams from text sources. Use when documenting system topology.
tools
Inspect JSON payloads and extract nested fields. Use when parsing API responses.
tools
--- name: no-description allowed-tools: Read --- # No Description This skill has no description and must be dropped with a warning.