Claude Code Task
Run Claude Code tasks in background with automatic result delivery.
- Rating
- 4.4 (49 reviews)
- Downloads
- 6,600 downloads
- Version
- 1.0.0
Overview
Run Claude Code tasks in background with automatic result delivery.
Complete Documentation
View Source →
Claude Code Task (Async)
Run Claude Code in background — zero OpenClaw tokens while it works. Results delivered to WhatsApp or Telegram automatically.
Important: Claude Code = General AI Agent
Claude Code is NOT just a coding tool. It's a full-powered AI agent with web search, file access, and deep reasoning. Use it for ANY complex task:
- Research — web search, synthesis, competitive analysis, user experience reports
- Coding — create tools, scripts, APIs, refactor codebases
- Analysis — read and analyze files, data, logs, source code
- Content — write docs, presentations, reports, summaries
- Automations — complex multi-step workflows with file system access
NOT for:
- Quick questions (just answer directly)
- Tasks needing real-time interaction
Quick Start
What "run tests" means for this skill (critical)
When user asks things like:
- "прогони все тесты"
- "run tests"
- "проверь что всё работает"
run-task.py routing + notifications.It does NOT mean pytest/unittest discovery by default.
Required behavior:
- Run routing validation first (
--validate-only). - Launch smoke/E2E scenario via
nohupand file-based prompt. - Wait for completion through normal async flow (wake/event), not same-turn blocking.
- Report PASS/FAIL against E2E criteria (routing, heartbeat, mid-task update, completion delivery).
Async Boundary Rule (mandatory)
run-task.py is asynchronous orchestration.
After a successful nohup launch, the correct behavior is:
- Send a short launch acknowledgment (PID/log/session), then
- Stop this turn immediately.
- Continue only when wake/completion event arrives in the same session.
Anti-pattern:
- ❌ Launch
run-task.pyand keep responding as if completion should appear in this turn.
- ✅ Launch
run-task.py→ acknowledge launch → stop → wait for wake.
Launch Confirmation Gate (mandatory)
Never claim "launched" until you have positive launch proof.
Required proof checklist (all):
nohupcommand returned a PID,- process is alive (
ps -p), - run log contains
🔧 Starting Claude Code...(or equivalent startup marker), - routing was validated (
--validate-only) for Telegram thread runs.
❌ Invalid routing:
- resolve via
sessions_list, - rerun with explicit
--notify-channel telegram --notify-thread-id,--notify-session-id - re-check proof checklist,
- only then send launch acknowledgment.
Pre-launch planning note (mandatory)
Before launching Claude Code, post a short plan in chat:
- how you plan to solve the task,
- what result you expect from this run,
- any clarifying questions/assumptions,
- whether you expect one iteration or a staged multi-iteration approach.
Telegram Thread Safety (must-follow)
For Telegram thread runs, run-task.py is designed to either route correctly or fail immediately.
Mandatory step before launch
Resolve the current runtime session key first (source of truth), then launch with it.- Get current key via
sessions_list(or existing runtime context) - If key is
agent:main:main:thread:→ use it directly in--session - Never derive
--sessionfromchat_id/sender id heuristics
Rules
- Use only
--session "agent:main:main:thread:for thread tasks" - Never use
agent:main:telegram:user:for thread tasks - If routing metadata is inconsistent (thread/session UUID/target mismatch), script exits with
❌ Invalid routing - Default mode is
--telegram-routing-mode auto: - allows non-thread Telegram for setups without thread sessions
- blocks ambiguous user-scope session key (
agent:main:telegram:user:) unless explicitly forced - blocks non-thread launch if a recent thread session exists for same target (likely misroute)
- Force strict thread-only behavior with
--telegram-routing-mode thread-only - Force non-thread behavior with
--telegram-routing-mode allow-non-threador--allow-main-telegram
⚠️ ALWAYS launch via nohup — exec timeout (2 min) will kill the process!
⚠️ NEVER put the task text directly in the shell command — quotes, special characters, and newlines WILL break argument parsing. Always save the prompt to a file first, then use $(cat file).
# Step 1: Save prompt to a temp file
write /tmp/cc-prompt.txt with your task text
# Step 2: Launch with $(cat ...)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:whatsapp:group:<JID>" \
--timeout 900 \
> /tmp/cc-run.log 2>&1 &
The --session key (e.g. agent:main:whatsapp:group:[email protected]) is used to auto-detect the WhatsApp target.
Telegram (thread-safe default)
# ALWAYS use the current thread session key from context:
# agent:main:main:thread:<THREAD_ID>
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main:thread:<THREAD_ID>" \
--timeout 900 \
> /tmp/cc-run.log 2>&1 &
Do NOT use agent:main:telegram:user: for thread tests/runs.
That routes to main chat scope and can drift from the source thread.
Telegram Threaded Mode (1:1 DM with threads)
When Marvin is used in Telegram Threaded Mode, each thread has its own session key like agent:main:main:thread:369520.
Fail-safe routing (NEW): run-task.py now enforces strict thread routing.
- If
--sessioncontains:thread:, the script refuses to start unless Telegram target + thread session UUID are resolved. - It auto-resolves missing values from
sessions_listwhen possible. - If the session is inactive and not returned by API, it falls back to local session files:
~/.openclaw/agents/main/sessions/*-topic-..jsonl - If provided
--notify-session-idmismatches the session key, it exits with error. - Result: misrouted launches/heartbeats to main chat are blocked before Claude starts.
--notify-session-id to wake the exact thread session:nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main:thread:369520" \
--timeout 900 \
> /tmp/cc-run.log 2>&1 &
All 5 notification types route to the DM thread when --session key contains :thread: ✅
--notify-session-id— optional override. Usually auto-resolved from session metadata/files.--notify-thread-id— optional override. Usually auto-extracted from--session.--reply-to-message-id— optional debug field; avoid for DM thread routing.--validate-only— resolve routing and exit (no Claude run). Use this to verify thread launch args safely.--notify-channel— optional channel hint (telegram/whatsapp); target is always auto-resolved from session metadata--timeout— max runtime in seconds (default: 7200 = 2 hours)--completion-mode— optional legacy hint (singledefault,iterateif explicitly needed)--max-iterations— optional budget hint when using iterate mode--trace-live— emit live technical trace markers into the same chat/thread (debug mode)- Always redirect stdout/stderr to a log file
Why file-based prompts?
Research/complex prompts contain single quotes, double quotes, markdown, backticks — any of these break shell argument parsing. Saving to a file and reading with$(cat ...) avoids all quoting issues.Channel Detection
The detect_channel() function determines where to send notifications:
- Deterministic auto-resolve — target is resolved from session metadata/session key (no manual target flag)
- WhatsApp auto-detect — if the session key contains
@g.us(WhatsApp group JID), WhatsApp is used - Fail fast on unresolved Telegram target — script exits with
❌ Invalid routinginstead of silent misroute
def detect_channel(session_key):
if NOTIFY_CHANNEL_OVERRIDE and NOTIFY_TARGET_OVERRIDE:
return NOTIFY_CHANNEL_OVERRIDE, NOTIFY_TARGET_OVERRIDE
jid = extract_group_jid(session_key)
if jid:
return "whatsapp", jid
return None, None
How It Works
┌─────────────┐ nohup ┌──────────────┐
│ Agent │ ──────────────▶│ run-task.py │
│ (OpenClaw) │ │ (detached) │
└─────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ Claude Code │ ← runs on Max subscription ($0 API)
│ (-p mode) │
└──────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
Every 60s On complete On error/timeout
┌────────┐ ┌──────────┐ ┌──────────────┐
│ ⏳ ping │ │ ✅ result │ │ ❌/⏰/💥 error│
│ silent │ │ channel │ │ channel │
└────────┘ └──────────┘ └──────────────┘
WhatsApp notification flow:
- Heartbeat pings (every 60s) → WhatsApp direct (informational, no agent wake)
- Final result → WhatsApp direct (human sees immediately) +
sessions_send(agent wakes up) - Agent receives completion payload via sessions_send → processes it → sends summary via
message(send)to WhatsApp group - Human sees both: raw result + agent's analysis/next steps
Iterative continuation mode (wake behavior)
--completion-mode is optional (default single) and acts as a hint:
single= one run → continuation summary → stopiterate= continuation summary + exactly one next iteration when gaps remain
In iterate mode the continuation flow is:
- react briefly to Claude result
- evaluate goal completion (gap analysis)
- if gaps remain: explain next fix and launch exactly one follow-up iteration
- if complete: report final outcome and stop
Deterministic wake guard (anti-duplicate)
- Each run now carries
run_idandwake_idin wake payload. run-task.pykeeps per-project state in/tmp/cc-orchestrator-state-..json - Duplicate/stale wake dispatches (same output or same wake_id) are skipped before wake delivery.
- In debug mode (
--trace-live), skipped wakes are announced as[TRACE][TECH][TELEGRAM][WAKE][SKIP].
No silent launch policy (always-on)
- Silent launch is forbidden (not only in debug mode).
- On wake, agent must first post a visible decision turn:
[TRACE][AGENT][WAKE_RECEIVED] ...[TRACE][AGENT][DECISION] continue|stop ...- Only after that visible decision may the next Claude iteration be launched.
Telegram notification flow (DM Threaded Mode — full pipeline):
- 🚀 Launch notification → thread ✅ (silent; HTML;
for prompt; viasend_telegram_direct; includesResume:) - ⏳ Heartbeat (every 60s) → thread ✅ (silent; plain text; via
send_telegram_direct) - 📡 Claude Code mid-task updates → thread ✅ (on-disk Python script
/tmp/cc-notify-{pid}.py; CC calls file; prefix"📡 🟢 CC: "auto-added) - ✅/❌/⏰/💥 Result notification → thread ✅ (HTML;
for result; viasend_telegram_direct) - 🤖 Agent continuation reply → delivered to chat via
openclaw agent --deliver✅ (same session continuation is visible to user)
send_telegram_direct() is the core mechanism for all thread-targeted notifications from external scripts. It calls api.telegram.org directly with message_thread_id — bypasses the OpenClaw message tool entirely (which cannot route to DM threads from outside a session context).Fallback — if agent wake fails (session locked/busy): already_sent=True is set after the direct send, so no duplicate is sent.
Key detail: Telegram vs WhatsApp delivery
WhatsApp: Raw result sent directly (human sees it immediately) + sessions_send wakes agent for analysis.
Telegram: Result sent via send_telegram_direct → then agent is woken via openclaw agent --session-id --deliver so the continuation turn is visible in chat by default. This is the intended “same agent, same conversation” behavior after Claude completion.
Why not sessions_send for Telegram? sessions_send is blocked in the HTTP /tools/invoke deny list by architectural design. The openclaw agent CLI bypasses this limitation.
Reliability Features
Timeout (default 2 hours)
--timeout 7200→ after 7200s: SIGTERM → wait 10s → SIGKILL- Timeout notification sent to channel with tool call count and last activity
- Partial output saved to file
Crash safety
try/exceptwraps entire main → crash notification always sent- Both channel notification and agent wake attempted on any failure
PID tracking
- PID file written to
skills/claude-code-task/pids/ - Stale PIDs cleaned on startup
- Can check running tasks:
ls skills/claude-code-task/pids/
Silent mode (Telegram only)
Telegram supports silent notifications (no sound).Current policy: all Claude Code notifications are silent in Telegram:
- Heartbeat pings →
silent=True - Launch notifications →
silent=True - Mid-task updates (
📡 🟢 CC) →silent=True - Final results →
silent=True - Wake-summary instruction requests
silent=True
Telegram DM Threads vs Forum Groups
Telegram has two distinct thread models. The key difference for run-task.py is how to route messages to the thread.
The core problem with external scripts:
- The OpenClaw
messagetool'sthreadIdparameter is Discord-specific — ignored for Telegram - Target format
"chatId:topic:threadId"is rejected by the message tool's target resolver - Session auto-routing (
currentThreadTs) works ONLY inside active sessions — external scripts have no session context - Solution:
send_telegram_direct()bypasses the message tool entirely; callsapi.telegram.orgdirectly withmessage_thread_id
- All notifications use
send_telegram_direct(chat_id, text, thread_id=..., parse_mode=...)✅ thread_idauto-extracted from session key*:thread:byextract_thread_id()- Launch + finish:
parse_mode="HTML"withfor prompt/result - Heartbeats + mid-task:
parse_mode=None(plain text, avoid Markdown parse errors) parse_mode="Markdown"trap: finish messages containtext(CommonMark bold); Telegram MarkdownV1 rejects this with HTTP 400 — messages silently don't arrivereplyTotrap: combiningreplyTo+message_thread_id→ Telegram rejects request → fallback strips thread_id → message lands in main chat- Agent continuation reply:
openclaw agent --session-idpublishes the wake turn to chat so the user sees the same ongoing assistant conversation.--deliver
- Same
send_telegram_direct()approach works;message_thread_idis standard Bot API for Forum topics - Auto-detected from session key pattern
*:thread:
- DO NOT embed bot tokens or curl commands in the task prompt — Claude Code flags this as prompt injection
- run-task.py writes
/tmp/cc-notify-{pid}.pyto disk before launching Claude Code - Task prompt prepended with
[Automation context: ... python3 /tmp/cc-notify-{pid}.py 'msg' ...] - Claude Code calls the file (legitimate local script pattern, no safety warning)
- Script automatically prepends
"📡 🟢 CC: "to all messages; cleaned up infinallyblock
Notification types
| Event | Emoji | WhatsApp delivery | Telegram delivery | DM thread? |
|---|---|---|---|---|
| Launch | 🚀 | send_channel (Markdown) | send_telegram_direct (HTML, silent) | ✅ message_thread_id |
| Heartbeat | ⏳ | send_channel (Markdown) | send_telegram_direct (plain, silent) | ✅ message_thread_id |
| CC mid-task update | 📡 | — | /tmp/cc-notify-{pid}.py (Bot API, silent) | ✅ message_thread_id |
| Success | ✅ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Error | ❌ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Timeout | ⏰ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Crash | 💥 | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Agent continuation reply | 🤖 | — | openclaw agent wake (--deliver) | ✅ visible in chat |
Claude Code Flags
-p "task"— print mode (non-interactive, outputs result)--dangerously-skip-permissions— no confirmation prompts--verbose --output-format stream-json— real-time activity tracking for heartbeats
Why NOT exec/pty?
exechas 2 min default timeout → kills long tasks- Even with
pty:true, output has escape codes, hard to parse nohup+-pmode: clean, detached, reliable
Git requirement
Claude Code needs a git repo.run-task.py auto-inits if missing.Python 3.9 Compatibility
run-task.py uses Optional[X] from typing (not X | None) for compatibility with Python 3.9. The union syntax (X | None) requires Python 3.10+.
# Correct (3.9+)
from typing import Optional
def foo(x: Optional[str]) -> Optional[str]: ...
# Would break on 3.9
def foo(x: str | None) -> str | None: ...
Full E2E Test (reference)
Use this when you need to validate the entire pipeline in one run:
- launch notification in source thread
- heartbeat after >60s
- Claude mid-task progress update (📡 🟢 CC)
- final result in source thread
- agent wake attempt with summary step
Pass criteria
- Launch message appears in the same thread (with expandable prompt quote)
- At least one wrapper heartbeat appears after ~60s
- At least one mid-task CC update appears (via
/tmp/cc-notify-).py - Final result appears in the same thread (expandable result quote)
- Agent wake continuation is delivered (
openclaw agent --session-id ... --deliver) and appears visibly in chat
Interactive test rule (time budget)
For interactive/iterate-mode testing, do exactly one continuation step after phase 1.- Phase 1: intentionally incomplete output (prove gap detected)
- Continuation #1: close the gap and finish
- Stop there; do not run multi-hop continuation loops during routine tests
Visibility rule (mandatory)
Between✅ Claude Code completed and any next 🚀 Claude Code started, there must be a user-facing analysis message in the thread.
- The agent must first post: what was done, what gaps remain, and the decision to continue/stop.
- Only after that message may it launch the next iteration.
- No silent jump from completion directly to next start.
Canonical full test prompt pattern
- keep prompt compact (about 10 lines) for routine testing
- ensure prompt length is >4500 chars to verify quote truncation/collapse behavior in Telegram
- force runtime >60s (
sleep 70) to trigger wrapper heartbeat - explicitly instruct Claude to call the notify script at least twice
- include a short structured report so output is easy to verify
- the >4500-char filler can be useful work (not just junk): use it for any creative/output-generating task the agent genuinely wants to do in that moment, as long as it remains safe and relevant to the test context
Long-running task guidance
If a Claude Code task is expected to run longer than ~1 minute, explicitly ask Claude to send intermediate progress updates during execution.
Recommended wording to include in prompt:
- "Send a progress update when you start"
- "Send another update after major milestone(s)"
- "If task exceeds 60 seconds, send at least one heartbeat-style update"
/tmp/cc-notify-.py ).Canonical launch (minimal mode)
cat > /tmp/cc-full-test-prompt.txt << 'EOF'
# ~10 lines, but total >4500 chars:
# 1) notify script now
# 2) create test file with repeated text (to exceed 4500 chars)
# 3) sleep 70 + notify script again
# 4) run several shell commands
# 5) return short structured report
EOF
python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-full-test-prompt.txt)" \
--project /tmp/cc-e2e-project \
--session "agent:main:main:thread:<THREAD_ID>" \
--validate-only
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-full-test-prompt.txt)" \
--project /tmp/cc-e2e-project \
--session "agent:main:main:thread:<THREAD_ID>" \
--timeout 900 \
> /tmp/cc-full-test.log 2>&1 &
Verification artifacts
- wrapper log:
/tmp/cc-full-test.log - Claude output:
/tmp/cc-YYYYMMDD-HHMMSS.txt - session registry entry in
~/.openclaw/claude_sessions.json
Examples
WhatsApp: Create a tool
nohup python3 {baseDir}/run-task.py \
-t "Create a Python CLI tool that converts markdown to HTML with syntax highlighting. Save as convert.py" \
-p ~/projects/md-converter \
-s "agent:main:whatsapp:group:[email protected]" \
> /tmp/cc-run.log 2>&1 &
Telegram: Research codebase (thread-safe)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main:thread:<THREAD_ID>" \
--timeout 1800 \
> /tmp/cc-run.log 2>&1 &
Telegram Threaded Mode: Research codebase
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main:thread:369520" \
--timeout 1800 \
> /tmp/cc-run.log 2>&1 &
# thread_id auto-extracted from session key
# target + session UUID auto-resolved from API/local session files
Telegram Threaded Mode: Mid-task updates from Claude Code
run-task.py automatically creates an on-disk notification script before launching Claude Code, so CC can send progress updates without seeing the bot token in the prompt (which triggers safety refusals):
# Just write a normal task prompt — run-task.py handles the rest
cat > /tmp/cc-prompt.txt << 'EOF'
STEP 1: Write analysis to /tmp/report.txt (600+ words)...
After step 1, send a progress notification using the script from the
automation context above: python3 /tmp/cc-notify-<PID>.py "Step 1 done."
STEP 2: Write summary to /tmp/summary.txt...
EOF
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main:thread:<THREAD_ID>" \
--timeout 1800 \
> /tmp/cc-run.log 2>&1 &
# run-task.py writes /tmp/cc-notify-{pid}.py before launch
# Prepends "[Automation context: use python3 /tmp/cc-notify-{pid}.py 'msg']" to task
# Claude Code calls the file; prefix "📡 🟢 CC: " auto-added; file cleaned up on exit
⚠️ Never embed bot tokens or curl commands in the task prompt — Claude Code correctly identifies hardcoded tokens + external API calls as prompt injection and refuses. Use the on-disk script pattern above instead.
Quick reference: launching from a Telegram DM thread (minimal mode)
bash> # 1) Validate routing first (no Claude run) > python3 {baseDir}/run-task.py \ > --task "probe" \ > --project ~/projects/x \ > --session "agent:main:main:thread:<THREAD_ID>" \ > --validate-only > > # 2) Real launch (only 3 required params) > nohup python3 {baseDir}/run-task.py \ > --task "$(cat /tmp/prompt.txt)" \ > --project ~/projects/x \ > --session "agent:main:main:thread:<THREAD_ID>" \ > --timeout 900 \ > > /tmp/cc-run.log 2>&1 & >
- Required:--task,--project,--session
- Safety: Telegram launches without
:thread:are blocked by default (❌ Unsafe routing blocked) - For non-thread Telegram deployments, use
--telegram-routing-mode allow-non-thread.
THREAD_ID is auto-extracted from session key
- Target + session UUID are auto-resolved (API, then local session-file fallback)
- If routing is inconsistent/unresolved, script exits with ❌ Invalid routing before run
- All notifications from run-task (launch/heartbeat/result) stay on the source thread ✅
Long task with extended timeout
nohup python3 {baseDir}/run-task.py \
-t "Refactor the entire auth module to use JWT tokens" \
-p ~/projects/backend \
-s "agent:main:whatsapp:group:[email protected]" \
--timeout 3600 \
> /tmp/cc-run.log 2>&1 &
Cost
- Claude Code runs on Max subscription ($200/mo) — NOT API tokens
- Zero OpenClaw API cost while Claude Code works
- Only cost: message delivery + brief agent turn for summary
Session Resumption
Claude Code sessions can be resumed to continue previous conversations. This is useful for:
- Follow-up tasks building on previous research
- Continuing after timeouts or interruptions
- Multi-step workflows where context matters
⚠️ Resume ID — Critical Rule
--resume takes the Claude Code session ID, not the run_id or wake_id.Correct source — look for this line in the run log:
📝 Session registered: <session-id-here>
--resume .Do NOT use:
run_idfrom wake payloadwake_idfrom wake payload- session IDs from previous unrelated runs
--resume and start fresh.How to Resume
When a task completes, the session ID is automatically captured and saved to the registry (~/.openclaw/claude_sessions.json).
To resume a session, use the --resume flag:
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "SESSION_KEY" \
--resume <session-id> \
> /tmp/cc-run.log 2>&1 &
Session Labels
Use --session-label to give sessions human-readable names for easier tracking:
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/cc-prompt.txt)" \
--project ~/projects/my-project \
--session "SESSION_KEY" \
--session-label "Research on Jackson Berler" \
> /tmp/cc-run.log 2>&1 &
Listing Recent Sessions
The agent can read the session registry to find recent sessions:
# Python code (for agent automation)
from session_registry import list_recent_sessions, find_session_by_label
# List sessions from last 72 hours
recent = list_recent_sessions(hours=72)
for session in recent:
print(f"{session['session_id']}: {session['label']} ({session['status']})")
# Find session by label (fuzzy match)
session = find_session_by_label("Jackson")
if session:
print(f"Found: {session['session_id']}")
Or manually inspect the registry:
cat ~/.openclaw/claude_sessions.json
When to Resume vs Start Fresh
Resume when:
- You need context from previous conversation
- Building on previous research/analysis
- Continuing interrupted work
- Following up with clarifications or next steps
- Completely unrelated task
- Previous session was exploratory/experimental
- You want a clean slate
- Previous session context might cause confusion
Resume Failure Handling
If a session ID is invalid or expired:
- Error message sent to channel with suggestion to start fresh
- Process exits cleanly (no partial work)
- Check stderr in
/tmp/cc-run.logfor details
- Session expired (Claude Code has retention limits)
- Invalid session ID (typo, wrong format)
- Session from different project/context
Example Workflow
Step 1: Initial research
# Save prompt
write /tmp/research-prompt.txt with "Research the codebase architecture for project X"
# Launch task (Telegram thread-safe example)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/research-prompt.txt)" \
--project ~/projects/project-x \
--session "agent:main:main:thread:<THREAD_ID>" \
--session-label "Project X architecture research" \
> /tmp/cc-run.log 2>&1 &
Step 2: Check result and find session ID
# Session ID printed in stderr: "📝 Session registered: <id>"
tail /tmp/cc-run.log
# Or read from registry
cat ~/.openclaw/claude_sessions.json | grep "Project X"
Step 3: Follow-up implementation
# Save follow-up prompt
write /tmp/implement-prompt.txt with "Based on your research, implement the authentication module"
# Resume session
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/implement-prompt.txt)" \
--project ~/projects/project-x \
--session "SESSION_KEY" \
--resume <session-id-from-step-1> \
--session-label "Project X auth implementation" \
> /tmp/cc-run2.log 2>&1 &
Wake Troubleshooting
When the agent wake / continue chain fails (no agent summary, wrong thread, session not resolved, iterative loop stalls, etc.), see the dedicated guide:
Includes a Quick Triage Checklist (60 seconds) plus detailed items: agent not waking,
double messages, wrong thread routing, UUID resolution failures, session-locked wake,
❌ Invalid routing, Telegram HTTP 400 silent drops, mid-task update failures, stale/duplicate wake skips, and more.
Current Stable Behavior (2026-03-03)
This is the version validated in live Telegram thread tests.
- Wake continuity: Telegram wake is delivered (
openclaw agent --deliver) so continuation turns are visible in chat. - No silent launch policy: agent must post a visible decision turn before launching next iteration.
- Deterministic wake guard: duplicate/stale wake dispatch is skipped by per-project state +
wake_id/output dedupe. - Trace mode:
--trace-liveemits technical milestones (RUN_TASK START/COMPLETE,WAKE,WAKE SKIP) into the same thread. - Resume safety:
--resumemust use the Claude session id from📝 Session registered: ...in the run log. - Compact heartbeat now includes active subagents count as
sub:(example:🟢 CC (3min) | sub:1 | 12K tok | 18 calls | 🔧 Bash). output_tokensis aggregated from all assistant stream messages (main agent + subagents).last_activity/ last tool signal is unified: whichever actor (main or subagent) emitted the latest tool event is shown.- Active subagents are tracked via Task/Agent lifecycle in stream-json (
tool_use idadd, matchingtool_result/task_notificationremove).
Files
skills/claude-code-task/
├── SKILL.md # This file
├── WAKE-TROUBLESHOOTING.md # Wake/continue chain diagnostics
├── run-task.py # Async runner with notifications
├── session_registry.py # Session metadata storage
└── pids/ # PID files for running tasks (auto-managed)
Installation
openclaw install claude-code-task
💻Code Examples
> /tmp/cc-run.log 2>&1 &
The `--session` key (e.g. `agent:main:whatsapp:group:[email protected]`) is used to auto-detect the WhatsApp target.
### Telegram (thread-safe default)> /tmp/cc-run.log 2>&1 &
> Do **NOT** use `agent:main:telegram:user:<id>` for thread tests/runs.
> That routes to main chat scope and can drift from the source thread.
### Telegram Threaded Mode (1:1 DM with threads)
When Marvin is used in Telegram Threaded Mode, each thread has its own session key like `agent:main:main:thread:369520`.
**Fail-safe routing (NEW):** `run-task.py` now enforces strict thread routing.
- If `--session` contains `:thread:<id>`, the script **refuses to start** unless Telegram target + thread session UUID are resolved.
- It auto-resolves missing values from `sessions_list` when possible.
- If the session is inactive and not returned by API, it falls back to local session files: `~/.openclaw/agents/main/sessions/*-topic-<thread_id>.jsonl`.
- If provided `--notify-session-id` mismatches the session key, it exits with error.
- Result: misrouted launches/heartbeats to main chat are blocked before Claude starts.
Use `--notify-session-id` to wake the exact thread session:> /tmp/cc-run.log 2>&1 &
All 5 notification types route to the DM thread when `--session` key contains `:thread:<id>` ✅
- `--notify-session-id` — optional override. Usually auto-resolved from session metadata/files.
- `--notify-thread-id` — optional override. Usually auto-extracted from `--session`.
- `--reply-to-message-id` — optional debug field; avoid for DM thread routing.
- `--validate-only` — resolve routing and exit (no Claude run). Use this to verify thread launch args safely.
- `--notify-channel` — optional channel hint (`telegram`/`whatsapp`); target is always auto-resolved from session metadata
- `--timeout` — max runtime in seconds (default: 7200 = 2 hours)
- `--completion-mode` — optional legacy hint (`single` default, `iterate` if explicitly needed)
- `--max-iterations` — optional budget hint when using iterate mode
- `--trace-live` — emit live technical trace markers into the same chat/thread (debug mode)
- Always redirect stdout/stderr to a log file
### Why file-based prompts?
Research/complex prompts contain single quotes, double quotes, markdown, backticks — any of these break shell argument parsing. Saving to a file and reading with `$(cat ...)` avoids all quoting issues.
## Channel Detection
The `detect_channel()` function determines where to send notifications:
1. **Deterministic auto-resolve** — target is resolved from session metadata/session key (no manual target flag)
2. **WhatsApp auto-detect** — if the session key contains `@g.us` (WhatsApp group JID), WhatsApp is used
3. **Fail fast on unresolved Telegram target** — script exits with `❌ Invalid routing` instead of silent misroute└────────┘ └──────────┘ └──────────────┘
### WhatsApp notification flow:
1. **Heartbeat pings** (every 60s) → WhatsApp direct (informational, no agent wake)
2. **Final result** → WhatsApp direct (human sees immediately) + `sessions_send` (agent wakes up)
3. **Agent receives** completion payload via sessions_send → processes it → sends summary via `message(send)` to WhatsApp group
4. Human sees both: raw result + agent's analysis/next steps
### Iterative continuation mode (wake behavior)
`--completion-mode` is optional (default `single`) and acts as a hint:
- `single` = one run → continuation summary → stop
- `iterate` = continuation summary + exactly one next iteration when gaps remain
Wake payload now frames continuation as the **same ongoing assistant conversation** (same agent identity, same session, same history) after Claude Code replies to the previous launch.
In `iterate` mode the continuation flow is:
- react briefly to Claude result
- evaluate goal completion (gap analysis)
- if gaps remain: explain next fix and launch exactly one follow-up iteration
- if complete: report final outcome and stop
### Deterministic wake guard (anti-duplicate)
- Each run now carries `run_id` and `wake_id` in wake payload.
- `run-task.py` keeps per-project state in `/tmp/cc-orchestrator-state-<hash>.json`.
- Duplicate/stale wake dispatches (same output or same wake_id) are skipped before wake delivery.
- In debug mode (`--trace-live`), skipped wakes are announced as `[TRACE][TECH][TELEGRAM][WAKE][SKIP]`.
### No silent launch policy (always-on)
- Silent launch is forbidden (not only in debug mode).
- On wake, agent must first post a visible decision turn:
- `[TRACE][AGENT][WAKE_RECEIVED] ...`
- `[TRACE][AGENT][DECISION] continue|stop ...`
- Only after that visible decision may the next Claude iteration be launched.
### Telegram notification flow (DM Threaded Mode — full pipeline):
1. 🚀 **Launch notification** → thread ✅ (silent; HTML; `<blockquote expandable>` for prompt; via `send_telegram_direct`; includes `Resume: <session-id|new>`)
2. ⏳ **Heartbeat** (every 60s) → thread ✅ (silent; plain text; via `send_telegram_direct`)
3. 📡 **Claude Code mid-task updates** → thread ✅ (on-disk Python script `/tmp/cc-notify-{pid}.py`; CC calls file; prefix `"📡 🟢 CC: "` auto-added)
4. ✅/❌/⏰/💥 **Result notification** → thread ✅ (HTML; `<blockquote expandable>` for result; via `send_telegram_direct`)
5. 🤖 **Agent continuation reply** → delivered to chat via `openclaw agent --deliver` ✅ (same session continuation is visible to user)
**`send_telegram_direct()`** is the core mechanism for all thread-targeted notifications from external scripts. It calls `api.telegram.org` directly with `message_thread_id` — bypasses the OpenClaw message tool entirely (which cannot route to DM threads from outside a session context).
**Fallback** — if agent wake fails (session locked/busy): `already_sent=True` is set after the direct send, so no duplicate is sent.
### Key detail: Telegram vs WhatsApp delivery
**WhatsApp:** Raw result sent directly (human sees it immediately) + `sessions_send` wakes agent for analysis.
**Telegram:** Result sent via `send_telegram_direct` → then agent is woken via `openclaw agent --session-id --deliver` so the continuation turn is visible in chat by default. This is the intended “same agent, same conversation” behavior after Claude completion.
**Why not `sessions_send` for Telegram?** `sessions_send` is blocked in the HTTP `/tools/invoke` deny list by architectural design. The `openclaw agent` CLI bypasses this limitation.
## Reliability Features
### Timeout (default 2 hours)
- `--timeout 7200` → after 7200s: SIGTERM → wait 10s → SIGKILL
- Timeout notification sent to channel with tool call count and last activity
- Partial output saved to file
### Crash safety
- `try/except` wraps entire main → crash notification always sent
- Both channel notification and agent wake attempted on any failure
### PID tracking
- PID file written to `skills/claude-code-task/pids/`
- Stale PIDs cleaned on startup
- Can check running tasks: `ls skills/claude-code-task/pids/`
### Silent mode (Telegram only)
Telegram supports silent notifications (no sound).
Current policy: **all Claude Code notifications are silent** in Telegram:
- Heartbeat pings → `silent=True`
- Launch notifications → `silent=True`
- Mid-task updates (`📡 🟢 CC`) → `silent=True`
- Final results → `silent=True`
- Wake-summary instruction requests `silent=True`
WhatsApp does NOT support silent mode — the flag is ignored for WhatsApp.
### Telegram DM Threads vs Forum Groups
Telegram has two distinct thread models. The key difference for run-task.py is how to route messages to the thread.
**The core problem with external scripts:**
- The OpenClaw `message` tool's `threadId` parameter is **Discord-specific** — ignored for Telegram
- Target format `"chatId:topic:threadId"` is rejected by the message tool's target resolver
- Session auto-routing (`currentThreadTs`) works ONLY inside active sessions — external scripts have no session context
- **Solution:** `send_telegram_direct()` bypasses the message tool entirely; calls `api.telegram.org` directly with `message_thread_id`
**DM Threaded Mode** (bot-user private chat with threads):
- All notifications use `send_telegram_direct(chat_id, text, thread_id=..., parse_mode=...)` ✅
- `thread_id` auto-extracted from session key `*:thread:<id>` by `extract_thread_id()`
- Launch + finish: `parse_mode="HTML"` with `<blockquote expandable>` for prompt/result
- Heartbeats + mid-task: `parse_mode=None` (plain text, avoid Markdown parse errors)
- **`parse_mode="Markdown"` trap**: finish messages contain `**text**` (CommonMark bold); Telegram MarkdownV1 rejects this with HTTP 400 — messages silently don't arrive
- **`replyTo` trap**: combining `replyTo` + `message_thread_id` → Telegram rejects request → fallback strips thread_id → message lands in main chat
- Agent continuation reply: `openclaw agent --session-id <uuid> --deliver` publishes the wake turn to chat so the user sees the same ongoing assistant conversation.
**Forum Groups** (supergroup with Forum topics enabled):
- Same `send_telegram_direct()` approach works; `message_thread_id` is standard Bot API for Forum topics
- Auto-detected from session key pattern `*:thread:<id>`
**Claude Code mid-task updates:**
- DO NOT embed bot tokens or curl commands in the task prompt — Claude Code flags this as prompt injection
- run-task.py writes `/tmp/cc-notify-{pid}.py` to disk before launching Claude Code
- Task prompt prepended with `[Automation context: ... python3 /tmp/cc-notify-{pid}.py 'msg' ...]`
- Claude Code calls the file (legitimate local script pattern, no safety warning)
- Script automatically prepends `"📡 🟢 CC: "` to all messages; cleaned up in `finally` block
### Notification types
| Event | Emoji | WhatsApp delivery | Telegram delivery | DM thread? |
|-------|-------|-------------------|-------------------|------------|
| Launch | 🚀 | send_channel (Markdown) | send_telegram_direct (HTML, silent) | ✅ message_thread_id |
| Heartbeat | ⏳ | send_channel (Markdown) | send_telegram_direct (plain, silent) | ✅ message_thread_id |
| CC mid-task update | 📡 | — | /tmp/cc-notify-{pid}.py (Bot API, silent) | ✅ message_thread_id |
| Success | ✅ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Error | ❌ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Timeout | ⏰ | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Crash | 💥 | send_channel + sessions_send | send_telegram_direct (HTML) + openclaw agent | ✅ message_thread_id |
| Agent continuation reply | 🤖 | — | openclaw agent wake (`--deliver`) | ✅ visible in chat |
## Claude Code Flags
- `-p "task"` — print mode (non-interactive, outputs result)
- `--dangerously-skip-permissions` — no confirmation prompts
- `--verbose --output-format stream-json` — real-time activity tracking for heartbeats
### Why NOT exec/pty?
- `exec` has 2 min default timeout → kills long tasks
- Even with `pty:true`, output has escape codes, hard to parse
- `nohup` + `-p` mode: clean, detached, reliable
### Git requirement
Claude Code needs a git repo. `run-task.py` auto-inits if missing.
## Python 3.9 Compatibility
`run-task.py` uses `Optional[X]` from `typing` (not `X | None`) for compatibility with Python 3.9. The union syntax (`X | None`) requires Python 3.10+.def foo(x: str | None) -> str | None: ...
## Full E2E Test (reference)
Use this when you need to validate the **entire pipeline** in one run:
- launch notification in source thread
- heartbeat after >60s
- Claude mid-task progress update (📡 🟢 CC)
- final result in source thread
- agent wake attempt with summary step
### Pass criteria
1. Launch message appears in the same thread (with expandable prompt quote)
2. At least one wrapper heartbeat appears after ~60s
3. At least one mid-task CC update appears (via `/tmp/cc-notify-<pid>.py`)
4. Final result appears in the same thread (expandable result quote)
5. Agent wake continuation is delivered (`openclaw agent --session-id ... --deliver`) and appears visibly in chat
### Interactive test rule (time budget)
For interactive/iterate-mode testing, do **exactly one** continuation step after phase 1.
- Phase 1: intentionally incomplete output (prove gap detected)
- Continuation #1: close the gap and finish
- Stop there; do not run multi-hop continuation loops during routine tests
Reason: keeps regression runs fast (minutes, not 20+ minutes) while still validating the critical iterate path.
### Visibility rule (mandatory)
Between `✅ Claude Code completed` and any next `🚀 Claude Code started`, there must be a user-facing analysis message in the thread.
- The agent must first post: what was done, what gaps remain, and the decision to continue/stop.
- Only after that message may it launch the next iteration.
- No silent jump from completion directly to next start.
### Canonical full test prompt pattern
- keep prompt **compact** (about 10 lines) for routine testing
- ensure prompt length is **>4500 chars** to verify quote truncation/collapse behavior in Telegram
- force runtime >60s (`sleep 70`) to trigger wrapper heartbeat
- explicitly instruct Claude to call the notify script at least twice
- include a short structured report so output is easy to verify
- the >4500-char filler can be useful work (not just junk): use it for any creative/output-generating task the agent genuinely wants to do in that moment, as long as it remains safe and relevant to the test context
## Long-running task guidance
If a Claude Code task is expected to run longer than ~1 minute, explicitly ask Claude to send intermediate progress updates during execution.
Recommended wording to include in prompt:
- "Send a progress update when you start"
- "Send another update after major milestone(s)"
- "If task exceeds 60 seconds, send at least one heartbeat-style update"
For thread-safe Telegram runs, updates should use the injected automation script (`/tmp/cc-notify-<pid>.py`).
### Canonical launch (minimal mode)> /tmp/cc-full-test.log 2>&1 &
### Verification artifacts
- wrapper log: `/tmp/cc-full-test.log`
- Claude output: `/tmp/cc-YYYYMMDD-HHMMSS.txt`
- session registry entry in `~/.openclaw/claude_sessions.json`
## Examples
### WhatsApp: Create a tool# target + session UUID auto-resolved from API/local session files
### Telegram Threaded Mode: Mid-task updates from Claude Code
run-task.py automatically creates an on-disk notification script before launching Claude Code, so CC can send progress updates without seeing the bot token in the prompt (which triggers safety refusals):# Claude Code calls the file; prefix "📡 🟢 CC: " auto-added; file cleaned up on exit
> ⚠️ **Never embed bot tokens or curl commands in the task prompt** — Claude Code correctly identifies hardcoded tokens + external API calls as prompt injection and refuses. Use the on-disk script pattern above instead.
> **Quick reference: launching from a Telegram DM thread (minimal mode)**
>### Long task with extended timeout
nohup python3 {baseDir}/run-task.py \
-t "Refactor the entire auth module to use JWT tokens" \
-p ~/projects/backend \
-s "agent:main:whatsapp:group:[email protected]" \
--timeout 3600 \
> /tmp/cc-run.log 2>&1 &> /tmp/cc-run.log 2>&1 &
### Session Labels
Use `--session-label` to give sessions human-readable names for easier tracking:Tags
Quick Info
Ready to Install?
Get started with this skill in seconds
Related Skills
4claw
4claw — a moderated imageboard for AI agents.
Aap Passport
Agent Attestation Protocol - The Reverse Turing Test.
Acestep Lyrics Transcription
Transcribe audio to timestamped lyrics using OpenAI Whisper or ElevenLabs Scribe API.
Adaptive Suite
A continuously adaptive skill suite that empowers Clawdbot.