ruvnet/ruflo · error · Error
Invalid command: contains shell metacharacters
Error message
Invalid command: contains shell metacharacters
What it means
ReleaseManager.execCommand() validates git command strings with validateCommand(), rejecting anything containing ; & | ` $ ( ) < >. Critically, prepareRelease() itself builds the commit command `git commit -m "chore(release): <version>"` — the literal parentheses in 'chore(release)' match the guard, so with the default commit: true the release flow throws this error at the commit step after the version bump and changelog were already written.
Source
Thrown at v3/@claude-flow/deployment/src/release-manager.ts:29
* Allowed git commands for security - prevents command injection
*/
const ALLOWED_GIT_COMMANDS = [
'git status --porcelain',
'git rev-parse HEAD',
'git log',
'git tag',
'git add',
'git commit',
'git describe',
];
/**
* Validate command against allowlist to prevent command injection
*/
function validateCommand(cmd: string): void {
// Check for shell metacharacters
if (/[;&|`$()<>]/.test(cmd)) {
throw new Error(`Invalid command: contains shell metacharacters`);
}
// Must start with an allowed command prefix
const isAllowed = ALLOWED_GIT_COMMANDS.some(prefix => cmd.startsWith(prefix));
if (!isAllowed) {
throw new Error(`Command not allowed: ${cmd.split(' ')[0]}`);
}
}
import type {
ReleaseOptions,
ReleaseResult,
PackageInfo,
GitCommit,
ChangelogEntry,
VersionBumpType
} from './types.js';
export class ReleaseManager {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Pass commit: false to prepareRelease() and create the release commit yourself: git add package.json CHANGELOG.md && git commit -m 'chore(release): x.y.z' — you get createTag: true for the annotated tag, which passes validation
- Report/upstream the bug: the built-in commit message conflicts with the module's own metacharacter regex
- Avoid parentheses, &&, $(), and redirects in any command string that reaches ReleaseManager
Example fix
// before
await manager.prepareRelease({ bumpType: 'patch' }); // throws at `git commit -m "chore(release): ..."`
// after
const r = await manager.prepareRelease({ bumpType: 'patch', commit: false });
execSync('git add package.json CHANGELOG.md');
execSync('git commit -m "chore(release): ' + r.newVersion + '"'); Defensive patterns
Strategy: validation
Validate before calling
// The library's own commit message contains parentheses, so bypass its commit step
await manager.prepareRelease({ bumpType, commit: false, createTag: true });
execSync('git add package.json CHANGELOG.md');
execSync(`git commit -m "chore(release): ${newVersion}"`); // your own exec, no allowlist Try / catch
const r = await manager.prepareRelease(options);
if (!r.success && /shell metacharacters/.test(r.error ?? '')) {
// version/changelog were written but commit/tag aborted: finish git steps manually
} Prevention
- Always pass commit: false to prepareRelease() until the built-in 'chore(release)' message is fixed against the metacharacter regex
- Check result.error on ReleaseResult — prepareRelease records this failure instead of throwing
When it happens
Trigger: prepareRelease() with commit: true (the default): the generated `git commit -m "chore(release): 1.2.3"` contains ( and ) and is rejected by validateCommand. Also triggered by caller-built strings passed through ReleaseManager containing &&, $(...), or parentheses in paths or messages.
Common situations: Any first run of prepareRelease() that gets far enough to commit — the tag step and `git add` pass, but the commit line trips the parenthesis check; release scripts with chained git commands.
Related errors
- Invalid git ref: contains unsafe characters
- Command not allowed: ${cmd.split(' ')[0]}
- Invalid git ref: suspicious pattern
- Invalid git ref: too long
- Invalid GCS object path: ${objectPath}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/bbfb88dbc0a72cf9.
Report an issue: GitHub.