Skip to content

refactor(content-guards): replace fragile bash expansion with explicit branching#40

Merged
JacobPEvans merged 3 commits intomainfrom
fix/markdown-unbound-var
Feb 16, 2026
Merged

refactor(content-guards): replace fragile bash expansion with explicit branching#40
JacobPEvans merged 3 commits intomainfrom
fix/markdown-unbound-var

Conversation

@JacobPEvans
Copy link
Owner

@JacobPEvans JacobPEvans commented Feb 16, 2026

Summary

Background

PR #39 (commit 2202144) fixed the config_flag[@]: unbound variable error using the bash workaround ${arr[@]+"${arr[@]}"}. While this works, it has significant drawbacks:

Changes

Before (fragile):

if ! markdownlint_output=$(markdownlint-cli2 "${config_flag[@]+"${config_flag[@]}"}" "$file_path" 2>&1); then

After (robust):

if [[ ${#config_flag[@]} -gt 0 ]]; then
  markdownlint_output=$(markdownlint-cli2 "${config_flag[@]}" "$file_path" 2>&1)
else
  markdownlint_output=$(markdownlint-cli2 "$file_path" 2>&1)
fi
if [[ $? -ne 0 ]]; then

Why This is Better

  1. Readable: Clear intent - if array has elements, use them; otherwise don't
  2. Maintainable: Standard bash patterns that any developer can understand
  3. Reliable: ${#arr[@]} works correctly with empty arrays under set -u on all bash versions
  4. Testable: Easy to verify both code paths independently

Test Suite (NEW)

Added the first automated test suite in the Claude Code plugin ecosystem using bats-core:

  • 8 test cases covering all critical scenarios
  • Regression prevention for unbound variable bugs
  • Cross-repo testing to verify config resolution from file's directory (not CWD)
  • CI-ready with TAP output format

Test coverage:

  • File type filtering (non-markdown, missing, dotfiles)
  • Config resolution (project vs fallback)
  • Cross-repo editing scenarios
  • Unbound variable regression prevention
  • Empty JSON input handling

Run with: bats tests/content-guards/markdown-validator/validate-markdown.bats

All 8 tests pass ✅

Test Plan

Verified on macOS with /bin/bash (bash 3.2) under set -euo pipefail:

  • Empty array path: Project with own markdownlint config (config_flag stays empty)
  • Non-empty array path: Project without config (uses fallback)
  • No "unbound variable" errors in either case
  • Validation logic still works correctly
  • All 8 automated tests pass

Related Issues

Related to #39 - same bug, better fix

🤖 Generated with Claude Code

…t branching

The previous fix (PR #39, commit 2202144) used ${config_flag[@]+"${config_flag[@]}"} to work around
bash 3.2's unbound variable error with empty arrays. While functional, this expansion is:
- Hard to read and understand
- Easy to get wrong (even the commit message described it backwards)
- Fragile across bash versions

This change replaces the arcane parameter expansion with explicit conditional branching:
- Check array length with ${#config_flag[@]} (works correctly under set -u)
- Separate code paths for empty/non-empty arrays
- Clear, maintainable, and works on all bash versions including macOS bash 3.2

Verified with /bin/bash (bash 3.2) under set -euo pipefail with both code paths.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 16, 2026 02:18
Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 9a61fbb in 5 seconds. Click for details.
  • Reviewed 18 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_pMswbHJ2tSuW8c3c

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@gemini-code-assist
Copy link

Summary of Changes

Hello @JacobPEvans, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the validate-markdown.sh script to enhance its robustness and readability. It replaces an obscure bash parameter expansion used for conditionally passing arguments to markdownlint-cli2 with clear, explicit conditional logic. This change not only improves the script's maintainability but also provides a more reliable solution for an "unbound variable" error encountered in bash 3.2, ensuring consistent behavior across different bash versions.

Highlights

  • Bash Script Refactoring: Replaced a complex and fragile bash parameter expansion (${config_flag[@]+"${config_flag[@]}"}) with explicit conditional branching (if [[ ${#config_flag[@]} -gt 0 ]]) for calling markdownlint-cli2.
  • Code Quality: Significantly improved the readability and maintainability of the validate-markdown.sh script by using standard bash patterns.
  • Bug Fix Robustness: Provided a more robust and reliable fix for the bash 3.2 "unbound variable" bug, addressing the same issue as a previous PR but with a superior approach.
Changelog
  • content-guards/scripts/validate-markdown.sh
    • Replaced the ${config_flag[@]+"${config_flag[@]}"} bash expansion with an if/else block.
    • Implemented explicit branching to call markdownlint-cli2 either with or without config_flag based on its array length.
    • Added a check for the exit status of markdownlint-cli2 after the conditional call.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a solid improvement, replacing a hard-to-read bash parameter expansion with a clear conditional statement. This significantly enhances the script's readability and maintainability, and also fixes a bug on older bash versions. I've added one suggestion to further refine the implementation by using an arguments array, which makes the code a bit more concise and avoids duplicating the command being called.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the markdown validation script to replace the arcane bash array expansion syntax ${config_flag[@]+"${config_flag[@]}"} (from PR #39) with explicit conditional branching. The goal is to improve code readability and maintainability while maintaining compatibility with bash 3.2's set -u strict mode.

Changes:

  • Replaces single-line conditional command execution with explicit if-else branching for array-dependent arguments
  • Separates command execution from exit code checking into two distinct steps

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

JacobPEvans and others added 2 commits February 15, 2026 21:30
Add comprehensive automated tests using bats-core to prevent regressions
in the markdown validator hook, specifically targeting the unbound variable
issue fixed in PR #39.

Test coverage:
- File type filtering (non-markdown, missing, dotfiles)
- Config resolution (project vs fallback)
- Cross-repo editing scenarios (CWD != file's directory)
- Unbound variable regression prevention
- Empty JSON input handling

This is the first automated test suite in the Claude Code plugin ecosystem.

Structure:
- tests/content-guards/markdown-validator/validate-markdown.bats (8 tests)
- tests/content-guards/markdown-validator/fixtures/ (test data)
- content-guards/README.md updated with testing documentation

All 8 tests pass successfully.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…tion

Addresses review feedback from copilot-pull-request-reviewer and gemini-code-assist.

The previous implementation incorrectly checked $? after the if-else block,
which captured the exit code of the conditional test [[ ${#config_flag[@]} -gt 0 ]]
instead of the markdownlint-cli2 command. This meant:

- If array check was true (exit 0), errors wouldn't be captured even if markdownlint failed
- If array check was false (exit 1), errors would be incorrectly captured even if markdownlint succeeded

Fixed by using the `if ! command_output=$(command ...)` pattern consistently,
matching the cspell implementation on line 120. While this introduces some
duplication, it's the most reliable approach for bash 3.2 with set -u.

Verified with bats test suite - all 8 tests pass.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@JacobPEvans
Copy link
Owner Author

@gemini-code-assist @copilot-pull-request-reviewer Thank you both for the excellent review feedback!

I've implemented the fix suggested by copilot-pull-request-reviewer that addresses both concerns:

Critical bug fixed: The $? exit code check now correctly captures the markdownlint command's exit status using the if ! command_output=$(command ...) pattern.

Why not the args array approach: While @gemini-code-assist's suggestion was elegant, it unfortunately triggers the same unbound variable error with bash 3.2 when the args array is empty under set -u. Testing confirmed this would fail 3 of our 8 bats test cases.

Solution: Kept the explicit if-else branching as suggested by @copilot-pull-request-reviewer. While this has some duplication, it's the most reliable approach for bash 3.2 compatibility and matches the proven pattern used for cspell validation.

All 8 automated tests pass ✅
All CI checks pass ✅

Latest commit: 0469b22

@gemini-code-assist
Copy link

Thank you, @JacobPEvans, for the detailed explanation and for thoroughly testing the different approaches! I appreciate you clarifying the unbound variable issue with bash 3.2 and set -u when the array is empty. It's great to hear that the explicit if-else branching provides a robust and reliable solution that passes all automated tests and CI checks. This ensures broad compatibility and maintainability, which are key for this script. Excellent work!

@JacobPEvans JacobPEvans merged commit cce5577 into main Feb 16, 2026
5 checks passed
@JacobPEvans JacobPEvans deleted the fix/markdown-unbound-var branch February 16, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant