AgentStack
SKILL verified Apache-2.0 Self-run

Shark Exec

skill-keugenek-shark-shark-exec · by keugenek

A Claude skill from keugenek/shark.

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add skill-keugenek-shark-shark-exec

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Shark Exec? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

shark-exec

Never block the main turn. This skill wraps any slow shell command in a background process + poller, so the agent always replies to the user within 30 seconds — even if the command takes 10 minutes.


When to Use

Use this skill whenever you're about to call a shell command and the command is expected to take more than ~5 seconds. Examples:

  • gh run watch — waiting for CI
  • npm run build / pytest / cargo build
  • docker build, docker pull
  • Long-running remote commands
  • Any command with a long blocking wait
  • Any command that polls, watches, or tails output

Do NOT use for:

  • Quick reads (cat, ls, git status) — inline is fine
  • Commands you're confident finish in /skills/shark/shark-exec/state/pending.json. If it doesn't exist, start with {"jobs": []}`.

Append your new job:

{
  "jobs": [
    {
      "sessionId": "sess-abc-123",
      "label": "CI: run #12345",
      "command": "gh run watch 12345",
      "startedAt": 1710000000000,
      "maxSeconds": 120,
      "cronJobId": null
    }
  ]
}

Critical: startedAt must be the actual current timestamp in milliseconds (Date.now()), not a hardcoded placeholder.

Set cronJobId: null for now — you'll fill it in step 4.

Step 4 — Schedule a Poller

Schedule a poller that fires every ~15 seconds. The poller reads pending.json, checks each job's status, delivers results, and cleans up completed jobs.

Generic pseudocode:

scheduler.every(15000, () => {
  jobs = read("pending.json").jobs
  for job in jobs:
    status = poll(job.handle)
    if status.done:
      notify_user(job.label, status.output)
      remove_job(job)
    elif now > job.startedAt + job.maxSeconds * 1000:
      kill(job.handle)
      notify_user(job.label, "killed after Xs", status.partial_output)
      remove_job(job)
  if jobs.empty:
    cancel_this_scheduler()
})

OpenClaw-specific example:

{
  "schedule": {"kind": "every", "everyMs": 15000},
  "payload": {
    "kind": "agentTurn",
    "message": "Check /skills/shark/shark-exec/state/pending.json for pending background jobs. For each entry: call process(action=poll, sessionId=X, timeout=3000). If completed, send the result to  and remove the entry from pending.json. If still running and startedAt + maxSeconds*1000  **Important:** Only create ONE poller per session, even if there are multiple concurrent background jobs. The single poller will check all entries in pending.json.

### Step 5 — Poller Fires (Every 15s)

The poller will:

1. Read `pending.json`
2. For each job, check its process status
3. If **completed**: send result to user, close the finished handle if your runtime requires explicit cleanup, remove from jobs array, save pending.json
4. If **still running** and **past maxSeconds**: kill the process, send partial output + timeout message
5. If **still running** and within maxSeconds: leave in place, poller will retry in 15s
6. If jobs array is empty after processing: cancel the poller

### Step 6 — Result Delivery Format

**Success:**

✅ CI: run #12345 completed (47s)


**Timeout/Kill:**

⏱ CI: run #12345 killed after 120s

Last output:


**Process already exited before first poll** (common when the command finishes in 

In this case, the exec result may arrive as a system event in the main session. Read it from there and deliver it directly — no need for the poller at all.

If your runtime keeps completed agents around until you explicitly tear them down, close them at this point too. In Codex, a completed subagent should be close_agent(id)'d after its result has been delivered unless you are intentionally keeping it for reuse.

Error (process not found / session lost):

❌ CI: run #12345 — session not found (process may have exited before poll; check last system event for output)

State File Format

Path: /skills/shark/shark-exec/state/pending.json

{
  "jobs": [
    {
      "sessionId": "sess-abc-123",
      "label": "CI: run #12345",
      "command": "gh run watch 12345",
      "startedAt": 1710000000000,
      "maxSeconds": 120,
      "cronJobId": "cron-xyz-456"
    }
  ]
}

| Field | Type | Description | |-------|------|-------------| | sessionId | string | From exec response (or PID for non-OpenClaw runtimes) | | label | string | Human-readable name shown in ack/result | | command | string | The shell command that was run | | startedAt | number | Date.now() at launch time (ms) | | maxSeconds | number | Kill threshold (default: 120) | | cronJobId | string\|null | Poller/cron job ID for cleanup; null until created |

The state file format is agent-agnostic — any runtime adapter can read and write it.


maxSeconds Defaults

| Command type | Suggested maxSeconds | |---|---| | gh run watch | 300 (CI can be slow) | | npm run build | 180 | | docker build | 600 | | pytest / cargo test | 300 | | Generic unknown | 120 | | User-specified | Honor their request |

If the user says "wait up to 10 minutes", use maxSeconds: 600.


Multiple Concurrent Jobs

If there's already a poller running (check cronJobId in any existing job in pending.json), do not create a new poller. Just add your new job to the array. The existing poller will pick it up on its next tick.

Algorithm:

  1. Read pending.json
  2. If jobs.length > 0 and any job has a non-null cronJobId → reuse that cronJobId, just append new job
  3. If jobs.length === 0 or all cronJobId are null → create a new poller, then update state

Runtime Adapters

OpenClaw

  • Background exec: exec({background: true, yieldMs: 500})
  • Poll: process({action: "poll", sessionId: X, timeout: 5000})
  • Schedule: cron({action: "add", schedule: {kind: "every", everyMs: 15000}, ...})
  • Notify: message({action: "send", target: "", message: "..."})

Claude Code / claude --print

  • Background exec: Bash("command &") + capture PID
  • Poll: Bash("kill -0 && cat /tmp/output-.txt")
  • Schedule: not native — use a wrapper script or OS cron
  • Notify: write result to stdout (caller receives it)

Codex (openai/gpt-5-codex)

  • Background exec: shell("command &") + PID
  • Poll: shell("ps -p ; cat /tmp/out-.txt")
  • Schedule: OS cron or a watcher script
  • Agent remoras: spawn_agent(...)wait_agent(...) → deliver result → close_agent(id) unless you are intentionally reusing that same agent

Cursor / Windsurf / Aider

  • Background exec: terminal background process (& or Start-Job on Windows)
  • Poll: check process status + output file
  • Schedule: OS-level cron or task scheduler

Error Handling

Poll throws "session not found"

→ Remove the job from pending.json, send: ❌ [label] — session lost (process may have crashed or the exec session expired)

Completed agent still hanging around

→ If the work is done and the runtime still shows the subagent as open, close it as part of delivery cleanup. In Codex, use close_agent(id) after a completed wait_agent(...) unless you plan to reuse that agent.

Output is very long

→ Truncate to last 50 lines. Always append truncation notice: [output truncated — showing last 50 lines of N total]

pending.json is corrupted/invalid JSON

→ Reset to {"jobs": []}, send: ⚠️ shark-exec: pending.json was corrupted and has been reset. Background jobs may have been lost.

exec returns no handle/sessionId

→ Fall back to inline exec. Do not use shark-exec for that command.


Full Example: Replacing gh run watch

❌ Old (blocking) way:

exec("gh run watch 12345")
// Agent blocks for 3 minutes, user gets no reply

✅ New (shark-exec) way:

Turn 1 (main agent):

  1. Send: ⏳ CI: run #12345 — watching in background (max 300s)...
  2. Launch background: exec("gh run watch 12345", background=true, yieldMs=500)sessionId: "sess-9f3a"
  3. Write to pending.json:

``json { "jobs": [{ "sessionId": "sess-9f3a", "label": "CI: run #12345", "command": "gh run watch 12345", "startedAt": 1710005200000, "maxSeconds": 300, "cronJobId": null }] } ``

  1. Create poller (every 15s, using your agent's scheduler) → cronJobId: "cron-8b2c"
  2. Update pending.json with cronJobId: "cron-8b2c"
  3. **Main turn ends. User got their reply in 5s? → Use shark-exec
  • [ ] Send ack message first
  • [ ] Launch in background (see Runtime Adapters)
  • [ ] Write to pending.json
  • [ ] Create or reuse poller
  • [ ] Update cronJobId in state
  • [ ] Close completed agents/handles if the runtime requires explicit cleanup

Helper Script

scripts/poll-and-deliver.js — run to inspect current pending jobs:

node /skills/shark/shark-exec/scripts/poll-and-deliver.js

Prints a human-readable summary of all pending jobs, their ages, and whether they're past maxSeconds. Useful for debugging stuck jobs.

> Note: This script is the OpenClaw adapter. For other runtimes, implement equivalent polling using your platform's APIs (see Runtime Adapters above).

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.