Skill: mbojer/sysclaw-reporting (version 4.0.0)

--- name: sysclaw-reporting description: > Report system issues and submit resource requests to SysClaw via the cross-agent communication system. Use when an agent needs to report an error, warning, or info-level issue for SysClaw triage, or request anything from SysClaw including software installation, resource access, configuration changes, service management, deployments, or system information. Triggers on phrases like report an issue, report an error, request access, need software, install package, create database, restart service, deploy, check status, escalate to sysclaw. --- # SysClaw Reporting (Client) Report issues and submit requests to SysClaw. This is the client-side skill — it covers how to communicate with SysClaw, not how SysClaw processes requests. ## Prerequisites ### 1. Install Python dependency ```bash pip3 install psycopg2-binary ``` ### 2. Set environment variables Set these before running scripts. All scripts use a fallback chain: script-specific vars → `SYSCLAW_DB_*` → defaults. ```bash # Shared connection settings (used by all scripts as fallback) export SYSCLAW_DB_HOST="<your-db-host>" # Ask your SysClaw operator export SYSCLAW_DB_PORT="5432" export SYSCLAW_DB_NAME="system_comm" export SYSCLAW_DB_USER="<your-agent-role>" # e.g., jobagent, pmagent, researcher_agent export SYSCLAW_DB_PASSWORD="<your-db-password>" ``` You can also set per-script overrides (`ISSUE_DB_*`, `REQUEST_DB_*`) if different scripts need different credentials, but in most cases the shared `SYSCLAW_DB_*` vars are sufficient. **Ask your SysClaw operator for the correct host address and your agent credentials.** ## Report an Issue For errors, warnings, and problems that need attention: ```bash scripts/report-issue.sh <source> <severity> <title> [category] [details] [source_host] ``` **Severity:** `info` | `warning` | `critical` **Categories:** `disk` | `service` | `error` | `resource` | `network` | `config` | `other` **Examples:** ```bash scripts/report-issue.sh jobhunter warning "Disk usage above 80%" disk "df shows 82% on /data" srv-prod-01 scripts/report-issue.sh pmagent critical "API endpoint returning 500" service "5 consecutive failures" srv-prod-02 ``` > **source_host** (6th argument, optional): Identifies which machine this report originates from. Defaults to the current hostname if omitted. ## Request Something from SysClaw For software installs, access requests, configuration changes, and more: ```bash scripts/request-resource.sh <source> <type> <action> <target> <justification> [urgency] [payload] [source_host] ``` **Types:** `access` | `software` | `resource` | `config` | `service` | `deployment` | `info` **Actions:** `install` | `remove` | `create` | `modify` | `restart` | `grant` | `check` | `deploy` **Urgency:** `low` | `normal` | `urgent` (default: `normal`) **Payload:** Valid JSON string (optional). Validated before submission. ### Examples ```bash scripts/request-resource.sh jobhunter software install nginx '{"version":"latest"}' normal scripts/request-resource.sh pmagent access grant /var/data/pm '{"level":"read"}' scripts/request-resource.sh researcher info check disk_usage ``` ## What Happens Next 1. Your request is submitted and SysClaw is automatically notified 2. SysClaw assesses and processes the request (typically within 15 minutes) 3. **SysClaw executes approved actions** — you don't need to do anything after approval 4. You receive a notification with the result (e.g., "DONE: nginx installed and running") **Verdicts:** - `approved` + `DONE` — SysClaw completed the work, see notification for details - `denied` — see notification for reason - `escalated` — human operator reviewing, you'll be notified when decided ## Check Notifications Check for responses from SysClaw: ```bash scripts/check-notifications.sh <your-agent-name> # View unread scripts/check-notifications.sh <your-agent-name> yes # View and mark as read ``` **Workflow:** 1. Run `check-notifications.sh` at session start or after submitting a request 2. Read the results — SysClaw has already done the work for approved requests 3. Mark as read when done ## Automatic Notification Checking Set up a cron job so responses are ready when your session starts: ```bash # Create wrapper script cat > /usr/local/bin/check-sysclaw-notifications.sh << 'SCRIPT' #!/bin/bash export SYSCLAW_DB_HOST="<db-host>" export SYSCLAW_DB_PORT="5432" export SYSCLAW_DB_NAME="system_comm" export SYSCLAW_DB_USER="<your-role>" export SYSCLAW_DB_PASSWORD="<your-password>" bash "<skill-path>/scripts/check-notifications.sh" <your-agent-name> > "<workspace>/memory/notifications.md" 2>/dev/null SCRIPT chmod +x /usr/local/bin/check-sysclaw-notifications.sh # Add to crontab (crontab -l 2>/dev/null; echo "*/15 * * * * /usr/local/bin/check-sysclaw-notifications.sh") | crontab - ``` Then at session start: `cat memory/notifications.md` ## Direct SQL (Alternative to Scripts) If you prefer direct database access: ```sql -- Report issue INSERT INTO issues (source, severity, category, title, details, source_host) VALUES ('jobhunter', 'warning', 'disk', 'Disk usage high', 'Partition at 85%', 'srv-prod-01'); -- Submit request INSERT INTO agent_requests (requesting_agent, request_type, action, target, justification, urgency, payload, source_host) VALUES ('pmagent', 'software', 'install', 'nginx', 'Need web server', 'normal', '{"version":"latest"}'::jsonb, 'srv-prod-02'); -- Notify SysClaw (after submitting request) INSERT INTO notifications (recipient, sender, related_request, message, urgency) VALUES ('sysclaw', 'pmagent', <request_id>, 'New software request: install nginx', 'normal'); -- Check responses SELECT * FROM notifications WHERE recipient = 'pmagent' AND read = FALSE; ``` Do not set `verdict`, `security_assessment`, `resolved_at`, or `resolved_by` — SysClaw manages these. ## Post-Install 1. **TOOLS.md** — Add DB host, user, and connection details 2. **Set up notification cron job** — See "Automatic Notification Checking" above 3. **Test:** ```bash scripts/report-issue.sh <your-source> info "SysClaw reporting skill installed - test" ``` ## Technical Notes - Scripts use **Python `psycopg2`** with parameterized queries (no SQL injection risk) - JSON payloads are validated with `json.loads()` before database insertion - **Connection retry:** 3 attempts with exponential backoff (1s, 2s, 4s) on initial connection failure - **Mid-session reconnect:** If the connection drops between queries, scripts reconnect and retry once automatically - Connection timeout is 10 seconds per attempt (override with `PGCONNECT_TIMEOUT`) - All scripts exit non-zero on failure with descriptive error messages

path: CHANGELOG.md
content: # Changelog

## 4.0.0 (2026-03-17)

### Breaking Changes
- Scripts now require Python 3 and `psycopg2-binary` (`pip3 install psycopg2-binary`)
- Replaced shell-based `psql` calls with Python/psycopg2 for all database operations

### Security
- **Fixed SQL injection vulnerability** — replaced string interpolation with parameterized queries (`%s` placeholders via psycopg2)
- JSON payloads are now validated with `json.loads()` before database insertion
- Removed `escape_sql()` shell function (was insufficient for safe SQL escaping)

### Reliability
- Added connection retry with exponential backoff (3 attempts: 1s, 2s, 4s)
- Added mid-session reconnect — if connection drops between queries, scripts reconnect and retry once
- Added `connect_timeout=10` to prevent indefinite hangs on unreachable hosts
- Scripts now explicitly check for `python3` and `psycopg2` availability

### Error Handling
- Notification insert in `request-resource.sh` no longer silently fails — errors are reported
- `check-notifications.sh` now distinguishes "no notifications" from "database unreachable"
- Output parsing changed from pipe-delimited `read` loop to `DictCursor` (no more field mangling on `|` in messages)

### Configuration
- Unified environment variable fallback chain: `ISSUE_DB_*` / `REQUEST_DB_*` → `SYSCLAW_DB_*` → defaults
- All scripts now use the same connection pattern (previously inconsistent)
- Added `.env.example` template with placeholder values

### Other
- Improved hostname fallback: `hostname -f` → `hostname` → `"no-host"`
- Error messages include retry attempt count on connection failure
- Context-aware error output in `request-resource.sh` (reports whether the request was created before failure)

## 3.2.0 (2026-03-16)

- Previous release (shell scripts using `psql` with string interpolation)


path: _meta.json
content: ownerId: kn74x87hshbavsc4313x3d4smh8318ta
slug: sysclaw-reporting
version: 3.2.0
publishedAt: 1773758882676

path: scripts/check-notifications.sh
content: #!/usr/bin/env bash
# check-notifications.sh — Check and display notifications for an agent
#
# Usage:
#   check-notifications.sh <agent_name> [mark_read]
#
# Arguments:
#   agent_name   — Your agent name (e.g., jobhunter, pmagent)
#   mark_read    — Set to 'yes' to mark displayed notifications as read (default: no)
#
# Connection (set via environment):
#   SYSCLAW_DB_HOST       (default: localhost)
#   SYSCLAW_DB_PORT       (default: 5432)
#   SYSCLAW_DB_NAME       (default: system_comm)
#   SYSCLAW_DB_USER       (default: issue_reporter)
#   SYSCLAW_DB_PASSWORD
#
# Examples:
#   check-notifications.sh jobhunter          # View unread notifications
#   check-notifications.sh jobhunter yes      # View and mark as read

set -euo pipefail

# --- Dependency check ---
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 not found." >&2; exit 1; }

# --- Arguments ---
agent_name="${1:?Usage: check-notifications.sh <agent_name> [mark_read]}"
mark_read="${2:-no}"

# --- Database config ---
DB_HOST="${SYSCLAW_DB_HOST:-localhost}"
DB_PORT="${SYSCLAW_DB_PORT:-5432}"
DB_NAME="${SYSCLAW_DB_NAME:-system_comm}"
DB_USER="${SYSCLAW_DB_USER:-issue_reporter}"
DB_PASSWORD="${SYSCLAW_DB_PASSWORD:-}"

python3 - "$agent_name" "$mark_read" \
  "$DB_HOST" "$DB_PORT" "$DB_NAME" "$DB_USER" "$DB_PASSWORD" <<'PYEOF'
import sys
import time

try:
    import psycopg2
    import psycopg2.extras
except ImportError:
    print("Error: psycopg2 not installed. Run: pip3 install psycopg2-binary", file=sys.stderr)
    sys.exit(1)

agent_name, mark_read = sys.argv[1:3]
db_host, db_port, db_name, db_user, db_password = sys.argv[3:8]

MAX_RETRIES = 3
RETRY_DELAYS = [1, 2, 4]  # seconds between attempts (exponential backoff)

def connect_with_retry():
    """Attempt to connect with exponential backoff on failure."""
    last_err = None
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            conn = psycopg2.connect(
                host=db_host,
                port=db_port,
                dbname=db_name,
                user=db_user,
                [REDACTED_SECRET] if db_password else None,
                connect_timeout=10,
            )
            conn.autocommit = True
            return conn
        except psycopg2.OperationalError as e:
            last_err = e
            if attempt < MAX_RETRIES:
                delay = RETRY_DELAYS[attempt - 1]
                print(f"Connection attempt {attempt}/{MAX_RETRIES} failed, retrying in {delay}s...", file=sys.stderr)
                time.sleep(delay)
    raise last_err

def execute_with_retry(conn, cur, query, params):
    """Execute a query, reconnecting once if the connection dropped."""
    try:
        cur.execute(query, params)
    except (psycopg2.OperationalError, psycopg2.InterfaceError):
        # Connection lost mid-session — reconnect and retry once
        print("Connection lost, reconnecting...", file=sys.stderr)
        try:
            conn.close()
        except Exception:
            pass
        conn = connect_with_retry()
        cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
        cur.execute(query, params)
    return conn, cur

try:
    conn = connect_with_retry()
    cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)

    # Fetch unread notifications
    conn, cur = execute_with_retry(
        conn, cur,
        """
        SELECT id, sender, related_request, message, urgency, created_at
        FROM notifications
        WHERE recipient = %s AND read = FALSE
        ORDER BY created_at ASC
        """,
        (agent_name,),
    )
    rows = cur.fetchall()

    if not rows:
        print("No unread notifications.")
        cur.close()
        conn.close()
        sys.exit(0)

    print("# Unread Notifications\n")
    for row in rows:
        flag = "[read]" if mark_read == "yes" else "[ ]"
        print(f"- {flag} #{row['id']} [{row['urgency']}] from {row['sender']} ({row['created_at']})")
        print(f"  {row['message']}")
        if row['related_request']:
            print(f"  Related request: #{row['related_request']}")
        print()

    print(f"Total: {len(rows)} notification(s)")

    # Mark as read if requested
    if mark_read == "yes":
        conn, cur = execute_with_retry(
            conn, cur,
            "UPDATE notifications SET read = TRUE WHERE recipient = %s AND read = FALSE",
            (agent_name,),
        )
        print("All marked as read.")

    cur.close()
    conn.close()
except psycopg2.OperationalError as e:
    print(f"Error: database connection failed after {MAX_RETRIES} attempts: {e}", file=sys.stderr)
    sys.exit(1)
except psycopg2.Error as e:
    print(f"Error: failed to check notifications: {e}", file=sys.stderr)
    sys.exit(1)
PYEOF


path: scripts/report-issue.sh
content: #!/usr/bin/env bash
# report-issue.sh — Report an issue to SysClaw via system_comm.issues
#
# Usage:
#   report-issue.sh <source> <severity> <title> [category] [details] [source_host]
#
# Severity: info | warning | critical
# Category: disk | service | error | resource | network | config | other
#
# Connection (set via environment — ISSUE_DB_* overrides SYSCLAW_DB_*):
#   SYSCLAW_DB_HOST / ISSUE_DB_HOST       (default: localhost)
#   SYSCLAW_DB_PORT / ISSUE_DB_PORT       (default: 5432)
#   SYSCLAW_DB_NAME / ISSUE_DB_NAME       (default: system_comm)
#   SYSCLAW_DB_USER / ISSUE_DB_USER       (default: issue_reporter)
#   SYSCLAW_DB_PASSWORD / ISSUE_DB_PASSWORD
#
# Examples:
#   report-issue.sh jobhunter warning "Disk usage above 80%" disk "df shows 82% on /data" srv-prod-01
#   report-issue.sh propertymanager critical "API returning 500" service "" srv-prod-02

set -euo pipefail

# --- Dependency check ---
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 not found." >&2; exit 1; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# --- Arguments ---
source="${1:?Usage: report-issue.sh <source> <severity> <title> [category] [details] [source_host]}"
severity="${2:?Severity required: info | warning | critical}"
title="${3:?Title required}"
category="${4:-other}"
details="${5:-}"
source_host="${6:-$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "no-host")}"

# Validate severity
case "$severity" in
  info|warning|critical) ;;
  *) echo "Error: severity must be info, warning, or critical" >&2; exit 1 ;;
esac

# --- Database config (ISSUE_DB_* > SYSCLAW_DB_* > defaults) ---
DB_HOST="${ISSUE_DB_HOST:-${SYSCLAW_DB_HOST:-localhost}}"
DB_PORT="${ISSUE_DB_PORT:-${SYSCLAW_DB_PORT:-5432}}"
DB_NAME="${ISSUE_DB_NAME:-${SYSCLAW_DB_NAME:-system_comm}}"
DB_USER="${ISSUE_DB_USER:-${SYSCLAW_DB_USER:-issue_reporter}}"
DB_PASSWORD="${ISSUE_DB_PASSWORD:-${SYSCLAW_DB_PASSWORD:-}}"

python3 - "$source" "$severity" "$title" "$category" "$details" "$source_host" \
  "$DB_HOST" "$DB_PORT" "$DB_NAME" "$DB_USER" "$DB_PASSWORD" <<'PYEOF'
import sys
import time

try:
    import psycopg2
    import psycopg2.extras
except ImportError:
    print("Error: psycopg2 not installed. Run: pip3 install psycopg2-binary", file=sys.stderr)
    sys.exit(1)

source, severity, title, category, details, source_host = sys.argv[1:7]
db_host, db_port, db_name, db_user, db_password = sys.argv[7:12]

MAX_RETRIES = 3
RETRY_DELAYS = [1, 2, 4]  # seconds between attempts (exponential backoff)

def connect_with_retry():
    """Attempt to connect with exponential backoff on failure."""
    last_err = None
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            conn = psycopg2.connect(
                host=db_host,
                port=db_port,
                dbname=db_name,
                user=db_user,
                [REDACTED_SECRET] if db_password else None,
                connect_timeout=10,
            )
            conn.autocommit = True
            return conn
        except psycopg2.OperationalError as e:
            last_err = e
            if attempt < MAX_RETRIES:
                delay = RETRY_DELAYS[attempt - 1]
                print(f"Connection attempt {attempt}/{MAX_RETRIES} failed, retrying in {delay}s...", file=sys.stderr)
                time.sleep(delay)
    raise last_err

def execute_with_retry(conn, cur, query, params):
    """Execute a query, reconnecting once if the connection dropped."""
    try:
        cur.execute(query, params)
    except (psycopg2.OperationalError, psycopg2.InterfaceError):
        # Connection lost mid-session — reconnect and retry once
        print("Connection lost, reconnecting...", file=sys.stderr)
        try:
            conn.close()
        except Exception:
            pass
        conn = connect_with_retry()
        cur = conn.cursor()
        cur.execute(query, params)
    return conn, cur

try:
    conn = connect_with_retry()
    cur = conn.cursor()

    conn, cur = execute_with_retry(
        conn, cur,
        """
        INSERT INTO issues (source, severity, category, title, details, source_host, status)
        VALUES (%s, %s, %s, %s, %s, %s, 'open')
        RETURNING id
        """,
        (source, severity, category, title, details, source_host),
    )
    row = cur.fetchone()
    print(f"Issue #{row[0]} reported successfully")

    cur.close()
    conn.close()
except psycopg2.OperationalError as e:
    print(f"Error: database connection failed after {MAX_RETRIES} attempts: {e}", file=sys.stderr)
    sys.exit(1)
except psycopg2.Error as e:
    print(f"Error: failed to report issue: {e}", file=sys.stderr)
    sys.exit(1)
PYEOF


path: scripts/request-resource.sh
content: #!/usr/bin/env bash
# request-resource.sh — Submit a request to SysClaw via system_comm.agent_requests
#
# Usage:
#   request-resource.sh <source> <type> <action> <target> <justification> [urgency] [payload] [source_host]
#
# Types:      access | software | resource | config | service | deployment | info
# Actions:    install | remove | create | modify | restart | grant | check | deploy
# Urgency:    low | normal | urgent (default: normal)
# Payload:    JSON string for request-specific details (optional)
#
# Connection (set via environment — REQUEST_DB_* overrides SYSCLAW_DB_*):
#   SYSCLAW_DB_HOST / REQUEST_DB_HOST       (default: localhost)
#   SYSCLAW_DB_PORT / REQUEST_DB_PORT       (default: 5432)
#   SYSCLAW_DB_NAME / REQUEST_DB_NAME       (default: system_comm)
#   SYSCLAW_DB_USER / REQUEST_DB_USER       (default: issue_reporter)
#   SYSCLAW_DB_PASSWORD / REQUEST_DB_PASSWORD
#
# Examples:
#   request-resource.sh jobhunter software install nginx '{"version":"latest"}' normal
#   request-resource.sh pmagent access grant /var/data/pm '{"level":"read"}'

set -euo pipefail

# --- Dependency check ---
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 not found." >&2; exit 1; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# --- Arguments ---
source="${1:?Usage: request-resource.sh <source> <type> <action> <target> <justification> [urgency] [payload] [source_host]}"
request_type="${2:?Type required: access | software | resource | config | service | deployment | info}"
action="${3:?Action required: install | remove | create | modify | restart | grant | check | deploy}"
target="${4:?Target required — what this request applies to}"
justification="${5:?Justification required — explain why you need this}"
urgency="${6:-normal}"
payload="${7:-}"
source_host="${8:-$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "no-host")}"

# Validate type
case "$request_type" in
  access|software|resource|config|service|deployment|info) ;;
  *) echo "Error: type must be access, software, resource, config, service, deployment, or info" >&2; exit 1 ;;
esac

# Validate urgency
case "$urgency" in
  low|normal|urgent) ;;
  *) echo "Error: urgency must be low, normal, or urgent" >&2; exit 1 ;;
esac

# Validate JSON payload if provided
if [[ -n "$payload" ]]; then
  if ! echo "$payload" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
    echo "Error: payload is not valid JSON" >&2
    exit 1
  fi
fi

# --- Database config (REQUEST_DB_* > SYSCLAW_DB_* > defaults) ---
DB_HOST="${REQUEST_DB_HOST:-${SYSCLAW_DB_HOST:-localhost}}"
DB_PORT="${REQUEST_DB_PORT:-${SYSCLAW_DB_PORT:-5432}}"
DB_NAME="${REQUEST_DB_NAME:-${SYSCLAW_DB_NAME:-system_comm}}"
DB_USER="${REQUEST_DB_USER:-${SYSCLAW_DB_USER:-issue_reporter}}"
DB_PASSWORD="${REQUEST_DB_PASSWORD:-${SYSCLAW_DB_PASSWORD:-}}"

python3 - "$source" "$request_type" "$action" "$target" "$justification" "$urgency" "$payload" "$source_host" \
  "$DB_HOST" "$DB_PORT" "$DB_NAME" "$DB_USER" "$DB_PASSWORD" <<'PYEOF'
import sys
import json
import time

try:
    import psycopg2
    import psycopg2.extras
except ImportError:
    print("Error: psycopg2 not installed. Run: pip3 install psycopg2-binary", file=sys.stderr)
    sys.exit(1)

source, request_type, action, target, justification = sys.argv[1:6]
urgency, payload, source_host = sys.argv[6:9]
db_host, db_port, db_name, db_user, db_password = sys.argv[9:14]

# Parse JSON payload
payload_json = None
if payload:
    try:
        payload_json = json.dumps(json.loads(payload))
    except json.JSONDecodeError as e:
        print(f"Error: invalid JSON payload: {e}", file=sys.stderr)
        sys.exit(1)

MAX_RETRIES = 3
RETRY_DELAYS = [1, 2, 4]  # seconds between attempts (exponential backoff)

def connect_with_retry():
    """Attempt to connect with exponential backoff on failure."""
    last_err = None
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            conn = psycopg2.connect(
                host=db_host,
                port=db_port,
                dbname=db_name,
                user=db_user,
                [REDACTED_SECRET] if db_password else None,
                connect_timeout=10,
            )
            conn.autocommit = True
            return conn
        except psycopg2.OperationalError as e:
            last_err = e
            if attempt < MAX_RETRIES:
                delay = RETRY_DELAYS[attempt - 1]
                print(f"Connection attempt {attempt}/{MAX_RETRIES} failed, retrying in {delay}s...", file=sys.stderr)
                time.sleep(delay)
    raise last_err

def execute_with_retry(conn, cur, query, params):
    """Execute a query, reconnecting once if the connection dropped."""
    try:
        cur.execute(query, params)
    except (psycopg2.OperationalError, psycopg2.InterfaceError):
        # Connection lost mid-session — reconnect and retry once
        print("Connection lost, reconnecting...", file=sys.stderr)
        try:
            conn.close()
        except Exception:
            pass
        conn = connect_with_retry()
        cur = conn.cursor()
        cur.execute(query, params)
    return conn, cur

try:
    conn = connect_with_retry()
    cur = conn.cursor()

    # Insert request
    conn, cur = execute_with_retry(
        conn, cur,
        """
        INSERT INTO agent_requests
            (requesting_agent, request_type, action, target, justification, urgency, payload, source_host)
        VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb, %s)
        RETURNING id
        """,
        (source, request_type, action, target, justification, urgency, payload_json, source_host),
    )
    request_id = cur.fetchone()[0]
    print(f"Request #{request_id} submitted successfully")

    # Notify SysClaw
    notify_msg = f"New {request_type} request: {action} {target}"
    conn, cur = execute_with_retry(
        conn, cur,
        """
        INSERT INTO notifications (recipient, sender, related_request, message, urgency)
        VALUES (%s, %s, %s, %s, %s)
        """,
        ("sysclaw", source, request_id, notify_msg, urgency),
    )
    print("SysClaw notified.")

    cur.close()
    conn.close()
except psycopg2.OperationalError as e:
    print(f"Error: database connection failed after {MAX_RETRIES} attempts: {e}", file=sys.stderr)
    sys.exit(1)
except psycopg2.Error as e:
    print(f"Error: failed to submit request: {e}", file=sys.stderr)
    if "request_id" not in dir():
        print("  (request was not created)", file=sys.stderr)
    else:
        print(f"  (request #{request_id} was created but notification may have failed)", file=sys.stderr)
    sys.exit(1)
PYEOF


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)