Skip to content

Conversation

@peedrr
Copy link

@peedrr peedrr commented Oct 11, 2025

Summary

Fixes #973

Replaces all $(...) command substitution syntax with backticks for shell portability across bash and zsh.

Problem

The Bash tool in Claude Code executes commands through the user's default shell, not necessarily bash. Commands using $(...) command substitution fail with parse errors when the default shell is zsh.

Error example:

(eval):1: parse error near ')'

Solution

Converted all $(...) to backticks `...` which works in both bash and zsh.

Test Results

Before (zsh with $(...)):

remote_url=$(git remote get-url origin)
# Result: (eval):1: parse error near ')'

After (zsh with backticks):

remote_url=`git remote get-url origin`
# Result: ✅ Works correctly

Changes

  • 217 instances converted across 29 files
  • Preserved multi-line format with backslash continuation for readability
  • No functional changes - only syntax conversion

Files Modified

  • 11 shell scripts (.sh)
  • 13 command files (commands/pm/*.md)
  • 5 rule files (rules/*.md)

Testing

  • ✅ Tested in zsh 5.9
  • ✅ Verified all converted commands execute correctly
  • ✅ No other zsh incompatibilities found

Related Issues

Summary by CodeRabbit

  • New Features
    • Epic list now groups epics by status (Planning, In Progress, Completed) with headers and total counts.
  • Bug Fixes
    • Pre-merge and remote-origin checks now fail fast with clear errors when conditions aren’t met.
    • Search shows an explicit “No matches” message when results are empty.
  • Documentation
    • Updated command examples for consistency across guides.
  • Chores
    • Standardized shell scripts to a consistent command-substitution style for improved consistency.

Replaces all $(...)  command substitution syntax with backticks for
shell portability. The Bash tool in Claude Code uses the user's default
shell (zsh in many cases), and $(...)  produces parse errors in zsh.

Changes:
- Converted 217 instances across 29 files
- Preserved multi-line format with backslash continuation
- No functional changes, only syntax conversion

Files modified:
- 11 shell scripts (.sh)
- 13 command files (commands/pm/*.md)
- 5 rule files (rules/*.md)

Fixes automazeio#973

Tested in zsh 5.9 - all commands now work correctly.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 2025

Walkthrough

Broad replacement of POSIX command substitution $(...) with backticks ... across commands, scripts, and rules. Two guard paths now explicitly exit 1 on validation failures (uncommitted changes in epic-merge; forbidden remote in issue-sync). Minor script restructuring and explicit messages/exit in search.sh; otherwise logic unchanged.

Changes

Cohort / File(s) Summary
PM Commands — syntax updates
ccpm/commands/pm/epic-refresh.md, ccpm/commands/pm/epic-start.md, ccpm/commands/pm/epic-sync.md, ccpm/commands/pm/issue-close.md, ccpm/commands/pm/test-reference-update.md
Replaced $(...) with backtick ... command substitutions; no control-flow changes.
PM Commands — guard/exit changes
ccpm/commands/pm/epic-merge.md, ccpm/commands/pm/issue-sync.md
Switched to backticks and added explicit exit 1 on validation failures (uncommitted changes; forbidden remote).
PM Scripts — syntax-only
ccpm/scripts/pm/epic-show.sh, .../epic-status.sh, .../prd-list.sh, .../prd-status.sh, .../status.sh, .../standup.sh, .../next.sh, .../blocked.sh, .../init.sh, .../epic-list.sh, .../validate.sh
Backtick substitution replaces $(...) across variable assignments and echos; functional behavior preserved.
PM Scripts — minor flow/output tweaks
ccpm/scripts/pm/in-progress.sh, ccpm/scripts/pm/search.sh
Backtick substitutions plus small refactors (intermediate vars); search adds explicit “No matches” messages and exit 0.
Rules docs
ccpm/rules/agent-coordination.md, .../datetime.md, .../github-operations.md, .../path-standards.md, .../strip-frontmatter.md
Documentation snippets updated to use backtick ... substitutions; no logic changes.
Path standards helpers
ccpm/scripts/check-path-standards.sh, ccpm/scripts/fix-path-standards.sh
Single-line substitutions (and echo formatting) switched to backticks; behavior unchanged.
Hook
ccpm/hooks/bash-worktree-fix.sh
Consistent replacement of $(...) with backticks in functions and main; no logic changes.
Test runner
ccpm/scripts/test-and-log.sh
Backticks used for basename/dirname substitutions in language-specific runners; no flow changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant EM as epic-merge.sh
  participant Git as Git

  U->>EM: Run epic-merge
  EM->>Git: git status --porcelain
  Git-->>EM: Output
  alt Uncommitted changes detected
    EM->>U: Print error
    EM-->>U: exit 1
  else Clean working tree
    EM->>EM: Proceed with merge steps
    EM-->>U: Continue script
  end
  %% Note: Added explicit exit 1 on dirty state
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant IS as issue-sync.sh
  participant Git as Git

  U->>IS: Run issue-sync
  IS->>Git: git remote get-url origin
  Git-->>IS: remote_url
  IS->>IS: Derive REPO
  alt REPO matches forbidden CCPM origin
    IS->>U: Print guard message
    IS-->>U: exit 1
  else Safe to sync
    IS->>IS: Continue sync logic
  end
  %% Note: Added explicit exit 1 on forbidden remote
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

A nibble of ticks, a hop through shells,

I swapped the $(...) where zsh rebels;

Two little exits stand guard at the gate,

“Commit or desist!” they dictate your fate.

Now scripts bound over fields so green—

Backticks flick, and flows stay clean. 🐇✅

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Several files include functional changes beyond the syntax conversion objective, such as added exit calls in validation blocks, new epic categorization logic in epic-list.sh, and enhanced messaging and exit behavior in search.sh that were not requested by the linked issue. These additions introduce behavior changes outside the scope of replacing command substitution syntax for zsh compatibility. They should be isolated to maintain focus on the core fix. Please remove or relocate these functional enhancements into separate pull requests so this PR remains solely focused on the backtick substitution fix for zsh compatibility.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely and accurately reflects the core change of replacing $(...) with backticks to fix zsh compatibility. It directly ties to the main objective without extraneous details and is clear to any teammate scanning the history.
Linked Issues Check ✅ Passed The pull request replaces all $(...) command substitutions with backticks across the repository as prescribed by issue #973, preserves the original formatting, and validates compatibility in zsh. It addresses the linked issue’s coding requirement for portable command substitution without leaving any residual $(...) instances. The applied changes align precisely with the preferred remediation in the linked issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c8e0e7 and d2bf192.

📒 Files selected for processing (29)
  • ccpm/commands/pm/epic-merge.md (6 hunks)
  • ccpm/commands/pm/epic-refresh.md (1 hunks)
  • ccpm/commands/pm/epic-start.md (1 hunks)
  • ccpm/commands/pm/epic-sync.md (11 hunks)
  • ccpm/commands/pm/issue-close.md (1 hunks)
  • ccpm/commands/pm/issue-sync.md (1 hunks)
  • ccpm/commands/pm/test-reference-update.md (2 hunks)
  • ccpm/hooks/bash-worktree-fix.sh (6 hunks)
  • ccpm/rules/agent-coordination.md (1 hunks)
  • ccpm/rules/datetime.md (2 hunks)
  • ccpm/rules/github-operations.md (2 hunks)
  • ccpm/rules/path-standards.md (1 hunks)
  • ccpm/rules/strip-frontmatter.md (2 hunks)
  • ccpm/scripts/check-path-standards.sh (1 hunks)
  • ccpm/scripts/fix-path-standards.sh (1 hunks)
  • ccpm/scripts/pm/blocked.sh (2 hunks)
  • ccpm/scripts/pm/epic-list.sh (3 hunks)
  • ccpm/scripts/pm/epic-show.sh (3 hunks)
  • ccpm/scripts/pm/epic-status.sh (5 hunks)
  • ccpm/scripts/pm/in-progress.sh (3 hunks)
  • ccpm/scripts/pm/init.sh (3 hunks)
  • ccpm/scripts/pm/next.sh (1 hunks)
  • ccpm/scripts/pm/prd-list.sh (3 hunks)
  • ccpm/scripts/pm/prd-status.sh (3 hunks)
  • ccpm/scripts/pm/search.sh (4 hunks)
  • ccpm/scripts/pm/standup.sh (5 hunks)
  • ccpm/scripts/pm/status.sh (3 hunks)
  • ccpm/scripts/pm/validate.sh (3 hunks)
  • ccpm/scripts/test-and-log.sh (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ccpm/scripts/fix-path-standards.sh (1)
ccpm/scripts/check-path-standards.sh (1)
  • print_success (16-18)
🪛 Shellcheck (0.11.0)
ccpm/scripts/pm/epic-status.sh

[warning] 38-38: status appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 39-39: progress appears unused. Verify use (or export if used externally).

(SC2034)

ccpm/scripts/pm/standup.sh

[warning] 7-7: today appears unused. Verify use (or export if used externally).

(SC2034)

ccpm/scripts/pm/validate.sh

[warning] 77-77: For loops over find output are fragile. Use find -exec or a while read loop.

(SC2044)

ccpm/scripts/pm/init.sh

[error] 77-77: [ .. ] can't match globs. Use [[ .. ]] or case statement.

(SC2081)

🔇 Additional comments (1)
ccpm/scripts/fix-path-standards.sh (1)

56-60: Backtick substitution breaks due to unescaped quotes

Switching to backticks here introduces a syntax error: the inner " around $file terminate the outer string before the backtick closes, so the script aborts at runtime. Pull the basename into a variable (still using backticks) before building the message so the quotes stay balanced.

-    if ! diff -q "$file" "$backup_file" >/dev/null 2>&1; then
-        print_success "File fixed: `basename "$file"`"
+    local display_name=`basename "$file"`
+    if ! diff -q "$file" "$backup_file" >/dev/null 2>&1; then
+        print_success "File fixed: $display_name"
         return 0
     else
-        print_info "File needs no changes: `basename "$file"`"
+        print_info "File needs no changes: $display_name"
         rm "$backup_file"  # Remove unnecessary backup
         return 1
     fi

Likely an incorrect or invalid review comment.

Comment on lines +23 to 24
[ -d "$dir" ] && echo "`basename "$dir"`"
done
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Backtick substitution breaks quoting here

echo " • \basename "$dir"`"now closes the surrounding double-quoted string at the inner"`, causing a parse error. Use escaped quotes inside the backticks (or assign to a temp variable) so the script stays syntactically valid.

-    [ -d "$dir" ] && echo "  • `basename "$dir"`"
+    [ -d "$dir" ] && echo "  • `basename \"$dir\"`"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[ -d "$dir" ] && echo "`basename "$dir"`"
done
[ -d "$dir" ] && echo "`basename \"$dir\"`"
done
🤖 Prompt for AI Agents
In ccpm/scripts/pm/epic-show.sh around lines 23-24 the command echo "  •
`basename "$dir"`" breaks the surrounding double quotes because of the inner
unescaped quotes; replace the backtick form with a safe command substitution or
temp variable (e.g. name=$(basename "$dir") then echo "  • $name") or use echo "
• $(basename "$dir")" so the inner quotes are not interpreted and the script
remains syntactically valid.

Comment on lines +29 to 31
echo " ⚠️ Missing epic.md in `basename "$epic_dir"`"
((warnings++))
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Escape quotes inside these backtick substitutions

Each of these lines now contains something like echo " … \basename "$foo"`", so the inner "` prematurely closes the outer string and the script fails to parse. Escape the inner quotes (or capture the basename into a variable first) before echoing.

-    echo "  ⚠️ Missing epic.md in `basename "$epic_dir"`"
+    echo "  ⚠️ Missing epic.md in `basename \"$epic_dir\"`"
...
-        echo "  ⚠️ Task `basename "$task_file" .md` references missing task: $dep"
+        echo "  ⚠️ Task `basename \"$task_file\" .md` references missing task: $dep"
...
-    echo "  ⚠️ Missing frontmatter: `basename "$file"`"
+    echo "  ⚠️ Missing frontmatter: `basename \"$file\"`"

Also applies to: 60-62, 79-80

🤖 Prompt for AI Agents
In ccpm/scripts/pm/validate.sh around lines 29 to 31 (and similarly at 60-62 and
79-80), the echo lines use backtick command substitutions containing unescaped
double quotes (e.g. `basename "$epic_dir"`), which prematurely close the outer
echo string; either escape the inner quotes inside the backticks (e.g. `basename
\"...\"`) or, preferably, assign the basename to a temporary variable before
echoing and reference that variable in the echo to avoid nested-quote issues.

@peedrr
Copy link
Author

peedrr commented Oct 12, 2025

Having slept on it, I think this is the wrong approach. While it does fix the immediate portability issue for this project in its current state, it doesn't get around the fundamental and ongoing issue: CC's models will continue to write Bash scripts that are designed to work in Bash, without consideration for other shells! it's only a matter of time before this pops up again.

The true fix is for Anthropic to address #7507.

I'll leave this PR open for now, in case you're interested in fixing for CCPM's current state only, otherwise feel free to reject it 👍🏼

gaucheclement pushed a commit to gaucheclement/Warhammer that referenced this pull request Oct 26, 2025
Applied PR #974 from automazeio/ccpm to fix shell compatibility issues.
Replaces all command substitution syntax from $(...) to backticks for
cross-shell portability between bash and zsh.

Changes:
- Convert $(...) to backticks in all shell scripts (30 files)
- Preserve $((arithmetic)) expressions
- Update commands, rules, hooks, and scripts
- Test all modified scripts for syntax correctness

This fixes parse errors when Claude Code Bash tool executes commands
through zsh as the default shell.

Related: automazeio/ccpm#974, automazeio/ccpm#973

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
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.

Commands fail on zsh: $(...) command substitution not portable

1 participant