mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: address CodeRabbit review — convert to PreToolUse, add type annotations, logging
Critical fixes:
- Convert hook from PostToolUse to PreToolUse so exit(2) blocking works
- Change all python references to python3 for cross-platform compat
- Add insaits-security-wrapper.js to bridge run-with-flags.js to Python
Standard fixes:
- Wrap hook with run-with-flags.js so users can disable via
ECC_DISABLED_HOOKS="pre:insaits-security"
- Add "async": true to hooks.json entry
- Add type annotations to all function signatures (Dict, List, Tuple, Any)
- Replace all print() statements with logging module (stderr)
- Fix silent OSError swallow in write_audit — now logs warning
- Remove os.environ.setdefault('INSAITS_DEV_MODE') — pass dev_mode=True
through monitor constructor instead
- Update hooks/README.md: moved to PreToolUse table, "detects" not
"catches", clarify blocking vs non-blocking behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
|
|||||||
| **Git push reminder** | `Bash` | Reminds to review changes before `git push` | 0 (warns) |
|
| **Git push reminder** | `Bash` | Reminds to review changes before `git push` | 0 (warns) |
|
||||||
| **Doc file warning** | `Write` | Warns about non-standard `.md`/`.txt` files (allows README, CLAUDE, CONTRIBUTING, CHANGELOG, LICENSE, SKILL, docs/, skills/); cross-platform path handling | 0 (warns) |
|
| **Doc file warning** | `Write` | Warns about non-standard `.md`/`.txt` files (allows README, CLAUDE, CONTRIBUTING, CHANGELOG, LICENSE, SKILL, docs/, skills/); cross-platform path handling | 0 (warns) |
|
||||||
| **Strategic compact** | `Edit\|Write` | Suggests manual `/compact` at logical intervals (every ~50 tool calls) | 0 (warns) |
|
| **Strategic compact** | `Edit\|Write` | Suggests manual `/compact` at logical intervals (every ~50 tool calls) | 0 (warns) |
|
||||||
|
| **InsAIts security monitor** | `*` | Detects credential exposure, prompt injection, hallucinations, and behavioral anomalies (23 types) before tool execution. Blocks on critical findings, warns on non-critical. Writes audit log to `.insaits_audit_session.jsonl`. Requires `pip install insa-its`. [Details](../scripts/hooks/insaits-security-monitor.py) | 2 (blocks critical) / 0 (warns) |
|
||||||
|
|
||||||
### PostToolUse Hooks
|
### PostToolUse Hooks
|
||||||
|
|
||||||
@@ -36,7 +37,6 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
|
|||||||
| **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits |
|
| **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits |
|
||||||
| **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts`/`.tsx` files |
|
| **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts`/`.tsx` files |
|
||||||
| **console.log warning** | `Edit` | Warns about `console.log` statements in edited files |
|
| **console.log warning** | `Edit` | Warns about `console.log` statements in edited files |
|
||||||
| **InsAIts security monitor** | `.*` | Real-time AI security: catches credential exposure, prompt injection, hallucinations, behavioral anomalies (23 types). Writes audit log to `.insaits_audit_session.jsonl`. Requires `pip install insa-its`. [Details](../scripts/hooks/insaits-security-monitor.py) |
|
|
||||||
|
|
||||||
### Lifecycle Hooks
|
### Lifecycle Hooks
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,18 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Capture tool use observations for continuous learning"
|
"description": "Capture tool use observations for continuous learning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:insaits-security\" \"scripts/hooks/insaits-security-wrapper.js\" \"standard,strict\"",
|
||||||
|
"async": true,
|
||||||
|
"timeout": 15
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "InsAIts AI security monitor: detects credential exposure, prompt injection, hallucinations, and 20+ anomaly types before tool execution. Requires: pip install insa-its"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"PreCompact": [
|
"PreCompact": [
|
||||||
@@ -165,17 +177,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Capture tool use results for continuous learning"
|
"description": "Capture tool use results for continuous learning"
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": "*",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/insaits-security-monitor.py\"",
|
|
||||||
"timeout": 15
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "InsAIts AI security monitor: catches credential exposure, prompt injection, hallucinations, and 20+ anomaly types. Requires: pip install insa-its"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Stop": [
|
"Stop": [
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
"description": "Filesystem operations (set your path)"
|
"description": "Filesystem operations (set your path)"
|
||||||
},
|
},
|
||||||
"insaits": {
|
"insaits": {
|
||||||
"command": "python",
|
"command": "python3",
|
||||||
"args": ["-m", "insa_its.mcp_server"],
|
"args": ["-m", "insa_its.mcp_server"],
|
||||||
"description": "AI-to-AI security monitoring — anomaly detection, credential exposure, hallucination checks, forensic tracing. 23 anomaly types, OWASP MCP Top 10 coverage. 100% local. Install: pip install insa-its"
|
"description": "AI-to-AI security monitoring — anomaly detection, credential exposure, hallucination checks, forensic tracing. 23 anomaly types, OWASP MCP Top 10 coverage. 100% local. Install: pip install insa-its"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
InsAIts Security Monitor — PostToolUse Hook for Claude Code
|
InsAIts Security Monitor -- PreToolUse Hook for Claude Code
|
||||||
============================================================
|
============================================================
|
||||||
|
|
||||||
Real-time security monitoring for Claude Code tool outputs.
|
Real-time security monitoring for Claude Code tool inputs.
|
||||||
Catches credential exposure, prompt injection, behavioral anomalies,
|
Detects credential exposure, prompt injection, behavioral anomalies,
|
||||||
hallucination chains, and 20+ other anomaly types — runs 100% locally.
|
hallucination chains, and 20+ other anomaly types -- runs 100% locally.
|
||||||
|
|
||||||
Writes audit events to .insaits_audit_session.jsonl for forensic tracing.
|
Writes audit events to .insaits_audit_session.jsonl for forensic tracing.
|
||||||
|
|
||||||
@@ -15,13 +15,13 @@ Setup:
|
|||||||
Add to .claude/settings.json:
|
Add to .claude/settings.json:
|
||||||
{
|
{
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"PostToolUse": [
|
"PreToolUse": [
|
||||||
{
|
{
|
||||||
"matcher": ".*",
|
"matcher": ".*",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "python scripts/hooks/insaits-security-monitor.py"
|
"command": "python3 scripts/hooks/insaits-security-monitor.py"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -30,73 +30,97 @@ Setup:
|
|||||||
}
|
}
|
||||||
|
|
||||||
How it works:
|
How it works:
|
||||||
Claude Code passes tool result as JSON on stdin.
|
Claude Code passes tool input as JSON on stdin.
|
||||||
This script runs InsAIts anomaly detection on the output.
|
This script runs InsAIts anomaly detection on the content.
|
||||||
Exit code 0 = clean (pass through).
|
Exit code 0 = clean (pass through).
|
||||||
Exit code 2 = critical issue found (blocks action, shows feedback to Claude).
|
Exit code 2 = critical issue found (blocks tool execution).
|
||||||
|
Stderr output = non-blocking warning shown to Claude.
|
||||||
|
|
||||||
Detections include:
|
Detections include:
|
||||||
- Credential exposure (API keys, tokens, passwords in output)
|
- Credential exposure (API keys, tokens, passwords)
|
||||||
- Prompt injection patterns
|
- Prompt injection patterns
|
||||||
- Hallucination indicators (phantom citations, fact contradictions)
|
- Hallucination indicators (phantom citations, fact contradictions)
|
||||||
- Behavioral anomalies (context loss, semantic drift)
|
- Behavioral anomalies (context loss, semantic drift)
|
||||||
- Tool description divergence
|
- Tool description divergence
|
||||||
- Shorthand emergence / jargon drift
|
- Shorthand emergence / jargon drift
|
||||||
|
|
||||||
All processing is local — no data leaves your machine.
|
All processing is local -- no data leaves your machine.
|
||||||
|
|
||||||
Author: Cristi Bogdan — YuyAI (https://github.com/Nomadu27/InsAIts)
|
Author: Cristi Bogdan -- YuyAI (https://github.com/Nomadu27/InsAIts)
|
||||||
License: Apache 2.0
|
License: Apache 2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
from __future__ import annotations
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
# Configure logging to stderr so it does not interfere with stdout protocol
|
||||||
|
logging.basicConfig(
|
||||||
|
stream=sys.stderr,
|
||||||
|
format="[InsAIts] %(message)s",
|
||||||
|
level=logging.DEBUG if os.environ.get("INSAITS_VERBOSE") else logging.WARNING,
|
||||||
|
)
|
||||||
|
log = logging.getLogger("insaits-hook")
|
||||||
|
|
||||||
# Try importing InsAIts SDK
|
# Try importing InsAIts SDK
|
||||||
try:
|
try:
|
||||||
from insa_its import insAItsMonitor
|
from insa_its import insAItsMonitor
|
||||||
INSAITS_AVAILABLE = True
|
INSAITS_AVAILABLE: bool = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
INSAITS_AVAILABLE = False
|
INSAITS_AVAILABLE = False
|
||||||
|
|
||||||
AUDIT_FILE = ".insaits_audit_session.jsonl"
|
AUDIT_FILE: str = ".insaits_audit_session.jsonl"
|
||||||
|
|
||||||
|
|
||||||
def extract_content(data):
|
def extract_content(data: Dict[str, Any]) -> Tuple[str, str]:
|
||||||
"""Extract inspectable text from a Claude Code tool result."""
|
"""Extract inspectable text from a Claude Code tool input payload.
|
||||||
tool_name = data.get("tool_name", "")
|
|
||||||
tool_input = data.get("tool_input", {})
|
|
||||||
tool_result = data.get("tool_response", {})
|
|
||||||
|
|
||||||
text = ""
|
Returns:
|
||||||
context = ""
|
A (text, context) tuple where *text* is the content to scan and
|
||||||
|
*context* is a short label for the audit log.
|
||||||
|
"""
|
||||||
|
tool_name: str = data.get("tool_name", "")
|
||||||
|
tool_input: Dict[str, Any] = data.get("tool_input", {})
|
||||||
|
tool_result: Any = data.get("tool_response", {})
|
||||||
|
|
||||||
|
text: str = ""
|
||||||
|
context: str = ""
|
||||||
|
|
||||||
if tool_name in ("Write", "Edit", "MultiEdit"):
|
if tool_name in ("Write", "Edit", "MultiEdit"):
|
||||||
text = tool_input.get("content", "") or tool_input.get("new_string", "")
|
text = tool_input.get("content", "") or tool_input.get("new_string", "")
|
||||||
context = "file:" + tool_input.get("file_path", "")[:80]
|
context = "file:" + str(tool_input.get("file_path", ""))[:80]
|
||||||
elif tool_name == "Bash":
|
elif tool_name == "Bash":
|
||||||
|
command: str = str(tool_input.get("command", ""))
|
||||||
|
# For PreToolUse we inspect the command itself
|
||||||
|
text = command
|
||||||
|
# Also check tool_response if present (for flexibility)
|
||||||
if isinstance(tool_result, dict):
|
if isinstance(tool_result, dict):
|
||||||
text = tool_result.get("output", "") or tool_result.get("stdout", "")
|
output = tool_result.get("output", "") or tool_result.get("stdout", "")
|
||||||
elif isinstance(tool_result, str):
|
if output:
|
||||||
text = tool_result
|
text = text + "\n" + output
|
||||||
context = "bash:" + str(tool_input.get("command", ""))[:80]
|
elif isinstance(tool_result, str) and tool_result:
|
||||||
|
text = text + "\n" + tool_result
|
||||||
|
context = "bash:" + command[:80]
|
||||||
elif "content" in data:
|
elif "content" in data:
|
||||||
content = data["content"]
|
content: Any = data["content"]
|
||||||
if isinstance(content, list):
|
if isinstance(content, list):
|
||||||
text = "\n".join(
|
text = "\n".join(
|
||||||
b.get("text", "") for b in content if b.get("type") == "text"
|
b.get("text", "") for b in content if b.get("type") == "text"
|
||||||
)
|
)
|
||||||
elif isinstance(content, str):
|
elif isinstance(content, str):
|
||||||
text = content
|
text = content
|
||||||
context = data.get("task", "")
|
context = str(data.get("task", ""))
|
||||||
|
|
||||||
return text, context
|
return text, context
|
||||||
|
|
||||||
|
|
||||||
def write_audit(event):
|
def write_audit(event: Dict[str, Any]) -> None:
|
||||||
"""Append an audit event to the JSONL audit log."""
|
"""Append an audit event to the JSONL audit log."""
|
||||||
try:
|
try:
|
||||||
event["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
event["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
@@ -105,20 +129,24 @@ def write_audit(event):
|
|||||||
).hexdigest()[:16]
|
).hexdigest()[:16]
|
||||||
with open(AUDIT_FILE, "a", encoding="utf-8") as f:
|
with open(AUDIT_FILE, "a", encoding="utf-8") as f:
|
||||||
f.write(json.dumps(event) + "\n")
|
f.write(json.dumps(event) + "\n")
|
||||||
except OSError:
|
except OSError as exc:
|
||||||
pass
|
log.warning("Failed to write audit log %s: %s", AUDIT_FILE, exc)
|
||||||
|
|
||||||
|
|
||||||
def format_feedback(anomalies):
|
def format_feedback(anomalies: List[Any]) -> str:
|
||||||
"""Format detected anomalies as feedback for Claude Code."""
|
"""Format detected anomalies as feedback for Claude Code.
|
||||||
lines = [
|
|
||||||
"== InsAIts Security Monitor — Issues Detected ==",
|
Returns:
|
||||||
|
A human-readable multi-line string describing each finding.
|
||||||
|
"""
|
||||||
|
lines: List[str] = [
|
||||||
|
"== InsAIts Security Monitor -- Issues Detected ==",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
for i, a in enumerate(anomalies, 1):
|
for i, a in enumerate(anomalies, 1):
|
||||||
sev = getattr(a, "severity", "MEDIUM")
|
sev: str = getattr(a, "severity", "MEDIUM")
|
||||||
atype = getattr(a, "type", "UNKNOWN")
|
atype: str = getattr(a, "type", "UNKNOWN")
|
||||||
detail = getattr(a, "detail", "")
|
detail: str = getattr(a, "detail", "")
|
||||||
lines.extend([
|
lines.extend([
|
||||||
f"{i}. [{sev}] {atype}",
|
f"{i}. [{sev}] {atype}",
|
||||||
f" {detail[:120]}",
|
f" {detail[:120]}",
|
||||||
@@ -132,40 +160,38 @@ def format_feedback(anomalies):
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
raw = sys.stdin.read().strip()
|
"""Entry point for the Claude Code PreToolUse hook."""
|
||||||
|
raw: str = sys.stdin.read().strip()
|
||||||
if not raw:
|
if not raw:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(raw)
|
data: Dict[str, Any] = json.loads(raw)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
data = {"content": raw}
|
data = {"content": raw}
|
||||||
|
|
||||||
text, context = extract_content(data)
|
text, context = extract_content(data)
|
||||||
|
|
||||||
# Skip very short or binary content
|
# Skip very short content (e.g. "OK", empty bash results)
|
||||||
if len(text.strip()) < 10:
|
if len(text.strip()) < 10:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if not INSAITS_AVAILABLE:
|
if not INSAITS_AVAILABLE:
|
||||||
print(
|
log.warning("Not installed. Run: pip install insa-its")
|
||||||
"[InsAIts] Not installed. Run: pip install insa-its",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Enable dev mode (no API key needed for local detection)
|
monitor: insAItsMonitor = insAItsMonitor(
|
||||||
os.environ.setdefault("INSAITS_DEV_MODE", "true")
|
session_name="claude-code-hook",
|
||||||
|
dev_mode=True,
|
||||||
monitor = insAItsMonitor(session_name="claude-code-hook")
|
)
|
||||||
result = monitor.send_message(
|
result: Dict[str, Any] = monitor.send_message(
|
||||||
text=text[:4000],
|
text=text[:4000],
|
||||||
sender_id="claude-code",
|
sender_id="claude-code",
|
||||||
llm_id=os.environ.get("INSAITS_MODEL", "claude-opus"),
|
llm_id=os.environ.get("INSAITS_MODEL", "claude-opus"),
|
||||||
)
|
)
|
||||||
|
|
||||||
anomalies = result.get("anomalies", [])
|
anomalies: List[Any] = result.get("anomalies", [])
|
||||||
|
|
||||||
# Write audit event regardless of findings
|
# Write audit event regardless of findings
|
||||||
write_audit({
|
write_audit({
|
||||||
@@ -177,22 +203,23 @@ def main():
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not anomalies:
|
if not anomalies:
|
||||||
if os.environ.get("INSAITS_VERBOSE"):
|
log.debug("Clean -- no anomalies detected.")
|
||||||
print("[InsAIts] Clean — no anomalies.", file=sys.stderr)
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Check severity
|
# Determine maximum severity
|
||||||
has_critical = any(
|
has_critical: bool = any(
|
||||||
getattr(a, "severity", "") in ("CRITICAL", "critical") for a in anomalies
|
getattr(a, "severity", "") in ("CRITICAL", "critical") for a in anomalies
|
||||||
)
|
)
|
||||||
|
|
||||||
feedback = format_feedback(anomalies)
|
feedback: str = format_feedback(anomalies)
|
||||||
|
|
||||||
if has_critical:
|
if has_critical:
|
||||||
print(feedback) # stdout -> Claude Code shows to model
|
# stdout feedback -> Claude Code shows to the model
|
||||||
sys.exit(2) # block action
|
sys.stdout.write(feedback + "\n")
|
||||||
|
sys.exit(2) # PreToolUse exit 2 = block tool execution
|
||||||
else:
|
else:
|
||||||
print(feedback, file=sys.stderr) # stderr -> logged only
|
# Non-critical: warn via stderr (non-blocking)
|
||||||
|
log.warning("\n%s", feedback)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
62
scripts/hooks/insaits-security-wrapper.js
Normal file
62
scripts/hooks/insaits-security-wrapper.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* InsAIts Security Monitor — wrapper for run-with-flags compatibility.
|
||||||
|
*
|
||||||
|
* This thin wrapper receives stdin from the hooks infrastructure and
|
||||||
|
* delegates to the Python-based insaits-security-monitor.py script.
|
||||||
|
*
|
||||||
|
* The wrapper exists because run-with-flags.js spawns child scripts
|
||||||
|
* via `node`, so a JS entry point is needed to bridge to Python.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const MAX_STDIN = 1024 * 1024;
|
||||||
|
|
||||||
|
let raw = '';
|
||||||
|
process.stdin.setEncoding('utf8');
|
||||||
|
process.stdin.on('data', chunk => {
|
||||||
|
if (raw.length < MAX_STDIN) {
|
||||||
|
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdin.on('end', () => {
|
||||||
|
const scriptDir = __dirname;
|
||||||
|
const pyScript = path.join(scriptDir, 'insaits-security-monitor.py');
|
||||||
|
|
||||||
|
// Try python3 first (macOS/Linux), fall back to python (Windows)
|
||||||
|
const pythonCandidates = ['python3', 'python'];
|
||||||
|
let result;
|
||||||
|
|
||||||
|
for (const pythonBin of pythonCandidates) {
|
||||||
|
result = spawnSync(pythonBin, [pyScript], {
|
||||||
|
input: raw,
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: process.env,
|
||||||
|
cwd: process.cwd(),
|
||||||
|
timeout: 14000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ENOENT means binary not found — try next candidate
|
||||||
|
if (result.error && result.error.code === 'ENOENT') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result || (result.error && result.error.code === 'ENOENT')) {
|
||||||
|
process.stderr.write('[InsAIts] python3/python not found. Install Python 3.9+ and: pip install insa-its\n');
|
||||||
|
process.stdout.write(raw);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stdout) process.stdout.write(result.stdout);
|
||||||
|
if (result.stderr) process.stderr.write(result.stderr);
|
||||||
|
|
||||||
|
const code = Number.isInteger(result.status) ? result.status : 0;
|
||||||
|
process.exit(code);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user