원클릭으로
safe-bash
Safe shell-command workflow.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Safe shell-command workflow.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Iterative multi-round codebase audit with diminishing-returns detection. Run 5-20+ rounds, each focusing on one specific area. Built from 19 rounds of dogfooding pi-crew on itself.
Pi TUI crew widget data sources, display priority, and rendering performance.
Multi-phase orchestration for planners and executors.
Spawn 3 adversarial subagents (Skeptic, Pragmatist, Critic) to evaluate a decision, architecture choice, or plan. Anti-anchoring: each role receives ONLY the question, not conversation history. Aggregates votes into consensus recommendation with dissent tracking. Use when facing critical decisions, architecture choices, security tradeoffs, or plan reviews where single-perspective analysis is insufficient.
Background worker, heartbeat, stale-run, crash-recovery, and deadletter workflow. Use when debugging stuck/dead workers or changing async run reliability.
Child Pi worker spawning, lifecycle callbacks, and failure modes.
| name | safe-bash |
| description | Safe shell-command workflow. |
| origin | pi-crew |
| triggers | ["run this command","execute bash","safe bash","destructive command","shell injection"] |
Use this skill whenever a task may execute shell commands. This skill covers cross-platform shell safety, destructive action confirmation, and Windows-specific patterns.
Every shell command is either read-only or mutating. Always report which it is.
pwd # print working directory
ls -la # list files
find . -name "*.ts" | head -20 # search without writing
rg "pattern" --type ts | head -20 # ripgrep without write
git status # inspect state
git log --oneline -5 # recent commits
git diff --staged # staged changes
npm view <pkg> # query registry (no install)
npx tsc --noEmit # typecheck (no write)
node -e "console.log(process.version)" # inspect version
npm install # changes node_modules
git commit # creates new commit
git push # publishes to remote
rm -rf <path> # DESTRUCTIVE
git reset --hard # rewrites history
npm publish # publishes to registry
// ❌ Never hardcode paths with forward slashes on Windows
const path = "D:/project/src/file.ts";
// ✅ Use path.join() or Node's path module
import * as path from "path";
const filePath = path.join(cwd, "src", "file.ts");
// ✅ Or use forward slashes that work on both
const filePath = "src/file.ts"; // relative paths work on both
// ✅ Preferred on Windows: argv-based execution (no shell)
import { spawn } from "child_process";
spawn("node", ["--version"], { stdio: "pipe" });
// ✅ If shell needed: use cmd /c explicitly
spawn("cmd", ["/c", "dir /b"], { stdio: "pipe" });
// ❌ Don't use cmd /c with complex commands as single string
spawn("cmd", ["/c", "node --version && npm test"], { stdio: "pipe" });
// Detect npm vs pnpm vs yarn
function detectPackageManager(cwd: string): "npm" | "pnpm" | "yarn" | "unknown" {
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
if (fs.existsSync(path.join(cwd, "yarn.lock"))) return "yarn";
return "npm";
}
# ❌ Unsafe: variable expansion without quoting
node -e "console.log('$PATH')" # breaks on spaces
# ✅ Safe: escape single quotes in the string
node -e "console.log(process.env.PATH)" # use env, not shell var
# ✅ For file paths with spaces
ls "D:/my project/src"
# ❌ Heredoc inside double quotes expands variables
cat << EOF
HOME is $HOME
EOF
# ✅ Use single quotes to prevent expansion
cat << 'EOF'
HOME is $HOME
EOF
# Linux/macOS: use timeout command
timeout 30 npm test # kill after 30 seconds
# Node.js: use AbortSignal
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
spawn("npm", ["test"], { signal: controller.signal });
# Windows: use /wait flag
start /wait cmd /c "npm test"
# Background: run and forget (no output capture)
npm test &
# Foreground with timeout
timeout 60 npm test || echo "Timed out or failed"
| Signal | Effect | Use case |
|---|---|---|
| SIGTERM (15) | Graceful termination request | Normal shutdown |
| SIGKILL (9) | Immediate forced termination | Unresponsive process |
| SIGINT (2) | Interrupt (Ctrl+C) | User cancel |
# Graceful: request termination
kill -15 <pid> # or: kill <pid>
# Force: immediate termination
kill -9 <pid> # or: kill -SIGKILL <pid>
# On Windows (cmd):
taskkill /pid <pid> /t /f # /t=tree, /f=force
When spawning subprocesses, child processes inherit the parent's signal handlers. Use signal: controller.signal in Node.js to give the child its own abort signal.
# Check if stdin is a terminal
[ -t 0 ] && echo "Interactive" || echo "Non-interactive"
# ❌ This will hang waiting for password
npm install -g typescript
# ✅ Use expect or non-interactive mode
sudo npm install -g typescript 2>/dev/null # ignores prompt
NPM_CONFIG_INTERACTIVE=false npm install -g typescript
#!/bin/bash
set -e # Exit immediately on error
set -o pipefail # Pipeline fails if any command fails
# Good for CI/CD scripts
npm test
# Capture exit code
npm test || {
echo "Tests failed with exit code $?"
exit 1
}
# Check for command existence
command -v npm >/dev/null 2>&1 || { echo "npm not found"; exit 1; }
For log parsing, strip ANSI escape codes:
// Strip ANSI color codes from output
function stripAnsi(text: string): string {
return text.replace(/\x1B\[[0-9;]*[mK]/g, "");
}
// Usage
const output = stripAnsi(child.stdout);
const hasFailure = output.includes("FAIL");
Or via shell:
# Strip ANSI codes using sed
npm test 2>&1 | sed 's/\x1B\[[0-9;]*[mK]//g'
// Detect node executable
import { execSync } from "child_process";
function findNode(): string {
// Try common paths
for (const candidate of [
process.execPath,
"node",
"C:\\Program Files\\nodejs\\node.exe",
"/usr/local/bin/node",
]) {
try {
execSync(`"${candidate}" --version`, { stdio: "ignore" });
return candidate;
} catch { /* continue */ }
}
return "node"; // fallback
}
Never execute destructive commands without explicit confirmation:
| Command | Risk | Confirmation needed |
|---|---|---|
rm -rf <path> | Permanent data loss | "Type the path to confirm" |
git reset --hard | Undo all changes | "Confirm with: YES" |
git clean -fd | Remove untracked files | "Confirm with: YES" |
npm publish | Public package | "Confirm version X.Y.Z" |
Confirmation pattern:
# Require user to type the exact path
read -p "Type the directory path to confirm deletion: " CONFIRM
if [ "$CONFIRM" = "$TARGET_PATH" ]; then
rm -rf "$TARGET_PATH"
else
echo "Aborted: paths do not match"
fi
Before executing shell commands, verify:
If ANY answer is NO → Stop. Classify and protect before executing.
rm -rf without path validation: Always double-check the path before rm -rf$? or capture exitCode from Node.js spawnsrc/utils/resolve-shell.ts — cross-platform shell detectionsrc/runtime/child-pi.ts — spawn, killProcessPid, signal handlingsrc/worktree/worktree-manager.ts — git commands via execFileSyncsrc/config/defaults.ts — platform detectioncd pi-crew
# Check exit code handling
node -e "const {spawnSync}=require('child_process'); console.log(spawnSync('false').status)"
# Test ANSI stripping
node -e "console.log('\x1B[32mgreen\x1B[0m'.replace(/\x1B\[[0-9;]*[mK]/g,''))"
# Verify cross-platform path
node -e "const p=require('path'); console.log(p.join('D:\\\\','project','src'))"
# TypeScript
npx tsc --noEmit