affaan-m/ECC · error · Error
File path contains unsafe shell characters
Error message
File path contains unsafe shell characters
What it means
post-edit-format.js throws this on Windows when the formatter binary is a .cmd file and must be spawned with shell:true. Because the shell enables command injection, the hook rejects file paths matching UNSAFE_PATH_CHARS (shell metacharacters such as quotes, &, |, ;) before spawning.
Solutions
- Rename the file or move the project to a path without shell metacharacters
- Use a non-.cmd formatter binary (node script or .exe) so shell:true is not needed
- Run the formatter manually (`npx biome check --write <file>`) for this file
- Fix PATH so a plain non-.cmd binary is resolved
Defensive patterns
Strategy: validation
Validate before calling
const UNSAFE_PATH_CHARS = /[&|;<>()"'`^%$!{}\[\],=?~*\s]/; // match hook's set
if (process.platform === 'win32' && !UNSAFE_PATH_CHARS.test(filePath)) {
formatFile(filePath); // safe to proceed
} Try / catch
try {
run({ filePath });
} catch (err) {
if (err.message === 'File path contains unsafe shell characters') {
console.warn(`Skipping shell-based format for: ${filePath}`);
return; // skip or format via non-shell path
}
throw err;
} Prevention
- Avoid shell metacharacters in project and file names, especially on Windows
- Prefer invoking formatters via `node <bin-js>` or .exe over .cmd shims
- Keep formatter resolution aware of platform so shell:true is only used when unavoidable
When it happens
Trigger: Editing a file on Windows whose path contains `&`, `|`, `;`, quotes, or other shell metacharacters, then triggering the PostToolUse format hook with a .cmd formatter such as biome.cmd.
Common situations: Project or temp directories with `&` or parentheses in their names, files created with quoted names, npm .cmd shims being resolved as the formatter binary on Windows.
Related errors
- Unsafe character in Windows linter argument
- Claude Code command contains characters that are unsafe for…
- Path traversal rejected
- Path traversal rejected
- result.stderr?.toString() || `Formatter exited with status
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a54faa4fe72e1071.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/hooks/post-edit-format.js:60
if (filePath && /\.(ts|tsx|js|jsx)$/.test(filePath)) {
try {
const resolvedFilePath = path.resolve(filePath);
const projectRoot = findProjectRoot(path.dirname(resolvedFilePath));
const formatter = detectFormatter(projectRoot);
if (!formatter) return rawInput;
const resolved = resolveFormatterBin(projectRoot, formatter);
if (!resolved) return rawInput;
// Biome: `check --write` = format + lint in one pass
// Prettier: `--write` = format only
const args = formatter === 'biome' ? [...resolved.prefix, 'check', '--write', resolvedFilePath] : [...resolved.prefix, '--write', resolvedFilePath];
if (process.platform === 'win32' && resolved.bin.endsWith('.cmd')) {
// Windows: .cmd files require shell to execute. Guard against
// command injection by rejecting paths with shell metacharacters.
if (UNSAFE_PATH_CHARS.test(resolvedFilePath)) {
throw new Error('File path contains unsafe shell characters');
}
const result = spawnSync(resolved.bin, args, {
cwd: projectRoot,
shell: true,
stdio: 'pipe',
timeout: 15000
});
if (result.error) throw result.error;
if (typeof result.status === 'number' && result.status !== 0) {
throw new Error(result.stderr?.toString() || `Formatter exited with status ${result.status}`);
}
} else {
execFileSync(resolved.bin, args, {
cwd: projectRoot,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000
});
}View on GitHub (pinned to 8321021c54)