Bug: Inline command execution (!) in slash commands fails with permission error
Bug Description
Slash commands that contain inline command execution syntax (!command``) fail with a permission error. The error occurs before the slash command can even execute, making inline command syntax unusable in custom slash commands.
Error Message
Error: Bash command permission check failed for pattern "!`git status --porcelain`":
This Bash command contains multiple operations. The following part requires approval:
export CLAUDE_PROJECT_DIR=/path/to/project
Root Cause
The issue occurs in the interaction between two functions:
- Command wrapper (
An5()in minified code) - Wraps every inline command withexport CLAUDE_PROJECT_DIR=<path>; <command> - Permission checker - Sees the wrapped command as "multiple operations" and blocks compound commands containing
export
Code flow:
// Inline command extracted from !`git status`
command = "git status --porcelain"
// An5() wraps with export
wrappedCommand = "export CLAUDE_PROJECT_DIR=/path; git status --porcelain"
// Permission checker blocks compound commands with export
if (!permissionCheck(wrappedCommand)) throw Error(); // ❌ Fails here
Reproduction Steps
Step 1: Create a test slash command
Create .claude/commands/test-inline.md:
---
description: Test inline command execution
---
## Context
- Current branch: !`git branch --show-current`
- Git status: !`git status --porcelain`
## Instructions
If you see this text, the bug is fixed!
Step 2: Execute the command
/test-inline
Step 3: Observe the error
The command fails with the permission error shown above. The slash command never executes because the inline commands fail during preprocessing.
Expected Behavior
- Inline command
!git status`` should be recognized - Command should execute with
CLAUDE_PROJECT_DIRset in the environment - Output should be injected into the slash command context
- Slash command should continue execution
Actual Behavior
- Inline command
!git status`` is recognized - Command is wrapped with
export CLAUDE_PROJECT_DIR=...; - Permission check sees compound command and blocks it
- Execution fails - slash command cannot continue
Proposed Fixes
Fix 1: Use Environment Variables Instead of Export (Recommended)
Instead of wrapping commands with export, pass environment variables via spawn options (like hooks already do):
// Current (broken)
command = `export CLAUDE_PROJECT_DIR=/path; ${command}`
exec(command)
// Proposed (works)
let env = {...process.env, CLAUDE_PROJECT_DIR: projectDir};
spawn(command, [], {env, shell: true})
This is exactly how hooks work and they don't have this issue.
Fix 2: Whitelist Self-Generated Export Pattern
Add an exception in the permission checker for the specific pattern Claude Code generates:
if (command.startsWith('export CLAUDE_PROJECT_DIR=') &&
command.includes('; ') &&
isSelfGenerated) {
return {behavior: "allow"};
}
Fix 3: Skip Export for Read-Only Commands
Only add export wrapper for commands that actually need it (plugins, certain tools).
Impact
Affected features:
- ❌ All slash commands with inline command execution (
!...``) - ❌ Dynamic context in slash commands (git status, branch info, etc.)
- ❌ Any automation relying on inline bash execution
Workaround:
Remove inline commands and have Claude execute them directly in the conversation:
<!-- Instead of this (broken): -->
- Current branch: !`git branch --show-current`
<!-- Use this (works): -->
First, run `git branch --show-current` to get the current branch.
System Information
- Platform: macOS (also likely affects Linux/Windows)
- Node Version: v20.18.1
- Shell: bash
Additional Context
Why This is a Security Improvement
The proposed fix (using spawn environment) is actually more secure than the current approach:
- ❌ Current: Export appears in command string → visible in logs, ps output
- ✅ Fixed: Environment set internally → not visible in command line
Consistency with Existing Code
Hooks and plugins already use spawn environment correctly:
// From hook execution code
let env = {...process.env, CLAUDE_PROJECT_DIR: projectDir};
spawn(command, [], {env, shell: true})
Inline commands should follow the same pattern.
Related Patterns
The inline command patterns that trigger this bug:
- Single-line:
!command`(e.g.,!git status`) - Multi-line:
`!\ncommand\n`
Both patterns are recognized by regex but fail when wrapped with export.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗