Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-hooks/check-defect-anchor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,17 @@ for file in "$@"; do
fi

# Skip if the file-level escape hatch is present
if grep -qE "$ESCAPE" <<<"$content"; then
if grep -qE "$ESCAPE" < <(printf '%s\n' "$content"); then
continue
fi

# Check for defect markers
if ! grep -qiE "$DEFECT_PATTERN" <<<"$content"; then
if ! grep -qiE "$DEFECT_PATTERN" < <(printf '%s\n' "$content"); then
continue
fi

# Defect marker found — check for OMN-XXXX reference
if ! grep -qE "$OMN_PATTERN" <<<"$content"; then
if ! grep -qE "$OMN_PATTERN" < <(printf '%s\n' "$content"); then
echo "DEFECT_ANCHOR_MISSING: $file — defect markers present but no OMN-XXXX reference found" >&2
echo " Ticket: $TICKET_REF" >&2
echo " Add an OMN-XXXX reference for each unresolved defect, or file a ticket first." >&2
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks/check-handoff-infra-health.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ for file in "$@"; do
continue
fi

if ! grep -qE "$SECTION_PATTERN" <<< "$content"; then
if ! grep -qE "$SECTION_PATTERN" < <(printf '%s\n' "$content"); then
echo "HANDOFF_MISSING_INFRA_HEALTH: $file — add ## Infra Health section" >&2
echo " Ticket: $TICKET_REF | Spec: OMN-8867" >&2
FOUND_VIOLATION=1
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-hooks/check-utc-timestamp-discipline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,17 @@ for file in "$@"; do
lineno=0
while IFS= read -r line; do
lineno=$((lineno + 1))
if grep -qE "$ESCAPE" <<<"$line"; then
if grep -qE "$ESCAPE" < <(printf '%s\n' "$line"); then
continue
fi
if grep -qiE "$MTIME_MARKER" <<<"$line" && grep -qE "$Z_TIME" <<<"$line"; then
if grep -qiE "$MTIME_MARKER" < <(printf '%s\n' "$line") && grep -qE "$Z_TIME" < <(printf '%s\n' "$line"); then
echo "UTC_MTIME_MISLABEL: ${file}:${lineno} — Z-suffixed time cited next to an mtime marker (${TICKET_REF})" >&2
echo " ${line}" >&2
echo " Local file mtimes are NOT UTC. Source the timestamp from 'date -u' or epoch conversion," >&2
echo " or drop the Z suffix and label the timezone explicitly. Escape hatch: 'utc-ok: <reason>'." >&2
FOUND_VIOLATION=1
fi
done <<<"$content"
done < <(printf '%s\n' "$content")
done

exit "$FOUND_VIOLATION"
4 changes: 2 additions & 2 deletions .pre-commit-hooks/reject-deploy-gate-skip-token.sh
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ for file in "$@"; do
continue
fi

if grep -qiE "$SKIP_PATTERN" <<< "$staged_content"; then
if grep -qiE "$SKIP_PATTERN" < <(printf '%s\n' "$staged_content"); then
# Check for explicit allowlist receipt in the staged content (also case-insensitive)
if grep -qiE "$ALLOWLIST_PATTERN" <<< "$staged_content"; then
if grep -qiE "$ALLOWLIST_PATTERN" < <(printf '%s\n' "$staged_content"); then
echo "WARNING: [skip-*] token found in $file but explicit approval receipt present — allowed." >&2
continue
fi
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks/reject-imperative-skill-patterns.sh
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ for arg in "$@"; do
label="${PATTERN_LABELS[$i]}"

# Filter out lines with the escape hatch annotation, then grep the rest.
violations=$(grep -nE "$pat" <<< "$content" | grep -v '#[[:space:]]*imperative-ok' || true)
violations=$(grep -nE "$pat" < <(printf '%s\n' "$content") | grep -v '#[[:space:]]*imperative-ok' || true)

if [[ -n "$violations" ]]; then
echo "ERROR: $file — imperative pattern detected: $label" >&2
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks/validate-occ-pr-stamp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ for file in "$@"; do
*) continue ;;
esac
body="$(read_staged "$file")" || continue
if grep -qiE "$STAMP_LINE_RE" <<< "$body"; then
if grep -qiE "$STAMP_LINE_RE" < <(printf '%s\n' "$body"); then
ARTIFACT_LABELS+=("$file")
ARTIFACT_BODIES+=("$body")
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
"name": "onex",
"description": "Full ONEX architecture plugin — intelligent agent routing, manifest injection, quality enforcement, polymorphic YAML agents, reusable skills, and custom slash commands. Internal dev tooling only; distinct MARKETPLACE (omninode-tools-dev) so `onex@omninode-tools-dev` never collides with the consumer-facing `onex@omninode-tools` install — the plugin name matches plugins/onex/.claude-plugin/plugin.json's own `name` field, uniqueness comes from marketplace scoping.",
"version": "2.4.22",
"version": "2.4.23",
"author": {
"name": "OmniNode",
"email": "dev@omninode.ai"
Expand Down
2 changes: 1 addition & 1 deletion plugins/onex/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "onex",
"description": "Unified ONEX architecture plugin with intelligent agent routing, manifest injection, quality enforcement, 53 polymorphic YAML agents, and 100+ reusable skills (invoked as /onex:<skill-name>) for comprehensive workflow automation and observability",
"version": "2.4.22",
"version": "2.4.23",
"author": {
"name": "OmniNode",
"email": "dev@omninode.ai"
Expand Down
2 changes: 1 addition & 1 deletion plugins/onex/hooks/lib/hook_refusal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ hook_record_refusal() {
--session-id "${SESSION_ID:-}" \
--agent-id "${AGENT_ID:-}" \
--payload-stdin \
<<<"$payload"
< <(printf '%s\n' "$payload")
) >>"${LOG_FILE:-/dev/null}" 2>&1 </dev/null &
disown 2>/dev/null || true
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ VERIFIER_RESULT="$(
REPO_HINT="$REPO_HINT" \
OMN_CLAIM_RESOLVER_REPO_ROOT="${_OMNICLAUDE_CALLER_CWD}" \
"$PYTHON_BIN" -m plugins.onex.hooks.lib.agent_result_verifier_runner \
<<< "$TURN_BODY" 2>&1
< <(printf '%s\n' "$TURN_BODY") 2>&1
)"
VERIFIER_EXIT=$?
set -e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ set +e
INJECTION_OUTPUT="$(
PYTHONPATH="${REPO_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" \
"$PYTHON_BIN" -m plugins.onex.hooks.lib.cost_accounting \
<<< "$HOOK_EVENT" 2>>"$LOG_FILE"
< <(printf '%s\n' "$HOOK_EVENT") 2>>"$LOG_FILE"
)"
PYTHON_EXIT=$?
set -e
Expand Down
2 changes: 1 addition & 1 deletion plugins/onex/hooks/scripts/post_tool_use_env_var_sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ while IFS= read -r VAR; do
# Append placeholder
printf '\n%s=<set_me> # auto-added by omniclaude hook\n' "$VAR" >> "$ENV_FILE"
ADDED+=("$VAR")
done <<< "$REQUIRED_VARS"
done < <(printf '%s\n' "$REQUIRED_VARS")

# -----------------------------------------------------------------------
# Report what was added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ FRICTION_FILE="${FRICTION_DIR}/${DATE_PREFIX}-${PATTERN}-${TS_NS}.yaml"
# Verbatim error excerpt — first 40 lines, YAML block-literal to preserve
# formatting without injection risk. YAML block scalar literal content does
# not need escaping of quotes/colons.
VERBATIM_EXCERPT=$(sed -n '1,40{s/^/ /;p;}' <<<"$TOOL_OUTPUT")
VERBATIM_EXCERPT=$(sed -n '1,40{s/^/ /;p;}' < <(printf '%s\n' "$TOOL_OUTPUT"))

cat > "$FRICTION_FILE" <<___KAFKA_POISON_FRICTION_EOF___ || true
id: kafka-poison-${PATTERN}-${SESSION_ID:0:8}-${TS_NS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ PYTHON_BIN="${PYTHON_CMD:-python3}"
# read the lane from it; the decision core gets the same bytes on its stdin.
_OMNICLAUDE_HOOK_PAYLOAD="$(cat)"
set +e
"$PYTHON_BIN" "$LIB_PY" <<<"$_OMNICLAUDE_HOOK_PAYLOAD"
"$PYTHON_BIN" "$LIB_PY" < <(printf '%s\n' "$_OMNICLAUDE_HOOK_PAYLOAD")
rc=$?
set -e
if [[ "$rc" -eq 2 ]]; then
Expand Down
4 changes: 2 additions & 2 deletions plugins/onex/hooks/scripts/pre_tool_use_bash_guards.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ _obg_job() {
ONEX_BASH_GUARDS_SLOT="$1"
ONEX_BASH_GUARDS_PHASE="$2"
local _obg_out _obg_rc
_obg_out="$(source "${_OBG_GUARDS[$1]}" <<<"$_OBG_INPUT")"
_obg_out="$(source "${_OBG_GUARDS[$1]}" < <(printf '%s\n' "$_OBG_INPUT"))"
_obg_rc=$?
printf '%s\0%s' "$_obg_rc" "$_obg_out"
}
Expand Down Expand Up @@ -235,7 +235,7 @@ if [[ ${#_OBG_BLOCKS[@]} -gt 1 ]]; then
_obg_args+=("${!_obg_out_var}")
done
jq -sc '{"decision": "block", "reason": ([.[] | (.reason // tostring)] | join("\n\n"))}' \
<<<"$(printf '%s\n' "${_obg_args[@]}")" 2>/dev/null \
< <(printf '%s\n' "${_obg_args[@]}") 2>/dev/null \
|| printf '{"decision": "block", "reason": "BLOCKED: %s Bash guards refused this command (OMN-20118); their reasons could not be combined."}\n' "${#_OBG_BLOCKS[@]}"
exit 2
fi
Expand Down
2 changes: 1 addition & 1 deletion plugins/onex/hooks/scripts/pre_tool_use_done_flip_guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ PYTHON_BIN="${PYTHON_CMD:-}"
[[ -n "$PYTHON_BIN" ]] || _done_flip_refuse "guard_error: no Python interpreter resolved for the guard"

set +e
"$PYTHON_BIN" "$LIB_PY" <<<"$_OMNICLAUDE_HOOK_PAYLOAD"
"$PYTHON_BIN" "$LIB_PY" < <(printf '%s\n' "$_OMNICLAUDE_HOOK_PAYLOAD")
rc=$?
set -e
if [[ "$rc" -eq 0 ]]; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ PYTHON_BIN="${PYTHON_CMD:-python3}"
# read the lane from it; the decision core gets the same bytes on its stdin.
_OMNICLAUDE_HOOK_PAYLOAD="$(cat)"
set +e
"$PYTHON_BIN" "$LIB_PY" <<<"$_OMNICLAUDE_HOOK_PAYLOAD"
"$PYTHON_BIN" "$LIB_PY" < <(printf '%s\n' "$_OMNICLAUDE_HOOK_PAYLOAD")
rc=$?
set -e
if [[ "$rc" -eq 2 ]]; then
Expand Down
12 changes: 6 additions & 6 deletions plugins/onex/hooks/scripts/user-prompt-submit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -504,11 +504,11 @@ _DELEGATION_SOCK="/tmp/omniclaude-delegation.sock"
if [[ -S "$_DELEGATION_SOCK" ]] && [[ -n "${SESSION_ID:-}" ]]; then
_POLL_REQ=$(jq -cn --arg sess "$SESSION_ID" '{"action": "poll_agentic", "session_id": $sess}')
_POLL_RESULT="$(printf '%s\n' "$_POLL_REQ" | socat -t2 - UNIX-CONNECT:"$_DELEGATION_SOCK" 2>/dev/null || echo "")"
if [[ -n "$_POLL_RESULT" ]] && jq -e '.agentic_completed == true' <<< "$_POLL_RESULT" >/dev/null 2>/dev/null; then
AGENTIC_WORK_PRODUCT="$(jq -r '.content // ""' <<< "$_POLL_RESULT")"
_AGENTIC_ITERATIONS="$(jq -r '.iterations // 0' <<< "$_POLL_RESULT")"
_AGENTIC_TOOLS="$(jq -r '.tool_calls_count // 0' <<< "$_POLL_RESULT")"
_AGENTIC_JOB_ID="$(jq -r '.job_id // "?"' <<< "$_POLL_RESULT")"
if [[ -n "$_POLL_RESULT" ]] && jq -e '.agentic_completed == true' < <(printf '%s\n' "$_POLL_RESULT") >/dev/null 2>/dev/null; then
AGENTIC_WORK_PRODUCT="$(jq -r '.content // ""' < <(printf '%s\n' "$_POLL_RESULT"))"
_AGENTIC_ITERATIONS="$(jq -r '.iterations // 0' < <(printf '%s\n' "$_POLL_RESULT"))"
_AGENTIC_TOOLS="$(jq -r '.tool_calls_count // 0' < <(printf '%s\n' "$_POLL_RESULT"))"
_AGENTIC_JOB_ID="$(jq -r '.job_id // "?"' < <(printf '%s\n' "$_POLL_RESULT"))"
log "Agentic job completed: job=$_AGENTIC_JOB_ID iterations=$_AGENTIC_ITERATIONS tools=$_AGENTIC_TOOLS content_chars=${#AGENTIC_WORK_PRODUCT}"
_TS_AGENTIC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "[$_TS_AGENTIC] [UserPromptSubmit] AGENTIC_COLLECTED job=$_AGENTIC_JOB_ID iterations=$_AGENTIC_ITERATIONS tool_calls=$_AGENTIC_TOOLS" >> "$TRACE_LOG"
Expand Down Expand Up @@ -718,7 +718,7 @@ if [[ "$_TICKET_INJECT_ENABLED" == "true" ]] && [[ -f "${HOOKS_LIB}/ticket_conte
# jq unavailable — use Python to extract ticket_context
_FP_TICKET_CONTEXT=$("$PYTHON_CMD" -c \
"import sys,json; d=json.load(sys.stdin); print(d.get('ticket_context',''))" \
<<< "$_FP_OUTPUT" 2>/dev/null) || _FP_TICKET_CONTEXT=""
< <(printf '%s\n' "$_FP_OUTPUT") 2>/dev/null) || _FP_TICKET_CONTEXT=""
if [[ -n "$_FP_TICKET_CONTEXT" ]]; then
FIRST_PROMPT_TICKET_CONTEXT="$_FP_TICKET_CONTEXT"
log "First-prompt ticket injection: success for $_FP_TICKET (jq unavailable)"
Expand Down
114 changes: 114 additions & 0 deletions tests/hooks/test_here_string_free_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""Prevent macOS Bash pipe deadlocks in hooks (OMN-20389)."""

from __future__ import annotations

import shutil
import subprocess
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]
HOOK_DIRECTORIES = (
".pre-commit-hooks",
"plugins/onex/hooks/scripts",
"plugins/onex/hooks/lib",
)
PAYLOAD_SIZES = (61440, 65536)


@pytest.fixture
def bash_binary() -> str:
"""Prefer the Homebrew Bash affected by the macOS pipe deadlock."""
homebrew_bash = Path("/opt/homebrew/bin/bash")
if homebrew_bash.exists():
return str(homebrew_bash)
bash = shutil.which("bash")
if bash is None:
pytest.skip("Bash unavailable for OMN-20389 hook regression tests")
return bash


def _write_payload(target: Path, size: int) -> None:
"""Write exactly size bytes of ordinary ASCII prose over many lines."""
line = "This ordinary prose describes routine project work and progress.\n"
payload = (line * (size // len(line) + 1))[:size]
target.write_text(payload, encoding="utf-8")


def _run_hook(
bash_binary: str, hook_name: str, target: Path
) -> subprocess.CompletedProcess[str]:
"""Run the real hook outside the repository with a hard timeout."""
hook = REPO_ROOT / ".pre-commit-hooks" / hook_name
try:
return subprocess.run(
[bash_binary, str(hook), str(target)],
cwd=target.parent,
capture_output=True,
check=False,
text=True,
timeout=60,
)
except subprocess.TimeoutExpired:
pytest.fail(f"OMN-20389: {hook_name} hung on {target.stat().st_size} bytes")


@pytest.mark.unit
def test_hooks_are_here_string_free() -> None:
"""Ratchet all shell hooks against here-strings, ignoring comment lines."""
violations: list[str] = []
for directory in HOOK_DIRECTORIES:
for script in sorted((REPO_ROOT / directory).rglob("*.sh")):
for lineno, line in enumerate(
script.read_text(encoding="utf-8").splitlines(), start=1
):
if line.lstrip().startswith("#"):
continue
if "<<<" in line:
violations.append(f"{script.relative_to(REPO_ROOT)}:{lineno}")
assert not violations, (
"OMN-20389: use < <(printf '%s\\n' \"$var\") instead of here-strings:\n"
+ "\n".join(violations)
)


@pytest.mark.unit
@pytest.mark.parametrize("size", PAYLOAD_SIZES)
def test_large_payload_skip_token_clean(
tmp_path: Path, bash_binary: str, size: int
) -> None:
"""Large clean markdown must pass without hanging."""
target = tmp_path / "clean.md"
_write_payload(target, size)
result = _run_hook(bash_binary, "reject-deploy-gate-skip-token.sh", target)
assert result.returncode == 0, result.stderr


@pytest.mark.unit
@pytest.mark.parametrize("size", PAYLOAD_SIZES)
def test_large_payload_skip_token_rejected(
tmp_path: Path, bash_binary: str, size: int
) -> None:
"""A skip token appended to large markdown must still be rejected."""
target = tmp_path / "skip.md"
_write_payload(target, size)
with target.open("a", encoding="utf-8") as stream:
stream.write("\n[" + "skip-deploy-gate: x]\n")
result = _run_hook(bash_binary, "reject-deploy-gate-skip-token.sh", target)
assert result.returncode != 0, "Expected the large payload's skip token to fail"
assert "contains a [skip-*] bypass token" in result.stderr


@pytest.mark.unit
@pytest.mark.parametrize("size", PAYLOAD_SIZES)
def test_large_payload_occ_non_artifact(
tmp_path: Path, bash_binary: str, size: int
) -> None:
"""Large markdown without Evidence lines must pass without a validator."""
target = tmp_path / "readme.md"
_write_payload(target, size)
result = _run_hook(bash_binary, "validate-occ-pr-stamp.sh", target)
assert result.returncode == 0, result.stderr
4 changes: 3 additions & 1 deletion tests/hooks/test_refusal_row_lane_omn19381.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,9 @@ def test_the_payload_is_never_passed_on_argv(self) -> None:
source = (HOOKS_DIR / "lib" / "hook_refusal.sh").read_text(encoding="utf-8")
body = source.split("hook_record_refusal()", 1)[1].split("\n}\n", 1)[0]
assert "--payload-stdin" in body
assert re.search(r'<<<\s*"\$payload"', body), body
# OMN-20389: a here-string deadlocks Homebrew bash 5.3 under macOS pipe
# pressure, so the payload reaches stdin through a process substitution.
assert re.search(r"<\s*<\(printf '%s\\n' \"\$payload\"\)", body), body
assert not re.search(r'--payload\s+"\$', body)

def test_hook_record_refusal_returns_before_a_slow_recorder(
Expand Down
Loading