OpenAI Codex CLI Runner
by @sbrin
Launch OpenAI Codex CLI async in background with automatic delivery to Telegram/WhatsApp. Use for coding, refactoring, codebase research, file generation, an...
clawhub install codex-cli-taskπ About This Skill
name: codex-cli-task description: "Launch OpenAI Codex CLI async in background with automatic delivery to Telegram/WhatsApp. Use for coding, refactoring, codebase research, file generation, and complex multi-step automations. NOT for quick one-off questions or real-time interactive tasks. Includes strict thread-safe routing + E2E operator validation workflow." metadata: { "openclaw": { "emoji": "π€", "requires": { "bins": ["codex", "python3"], "pythonModules": ["requests"], "config": ["gateway.auth.token", "gateway.tools.allow", "tools.sessions.visibility"], }, "config": { "stateDirs": ["~/.openclaw"] }, }, }
Codex Code Task (Async)
Run OpenAI Codex CLI in background β zero OpenClaw tokens while it works. Results delivered to WhatsApp or Telegram automatically.
Important: Codex = General AI Agent
Codex is NOT just a coding tool. In codex exec mode it is a general-purpose AI agent with file access, shell execution, optional web search, and deep reasoning.
Use it for:
Give it prompts the same way you'd talk to a smart human β natural language, focused on WHAT you need, not HOW to do it.
NOT for:
Quick Start
What "run tests" means for this skill (critical)
When user asks things like:
it means run the full E2E operator validation flow for run-task.py routing + notifications.
It does NOT mean plain pytest/unittest discovery by default.
Required behavior:
1. Run routing validation first (--validate-only).
2. Launch smoke/E2E scenario via nohup and file-based prompt.
3. Wait for completion through normal async flow, not same-turn blocking.
4. Report PASS/FAIL against E2E criteria: routing, heartbeat, mid-task update, completion delivery.
Use the canonical protocol: references/testing-protocol.md and the section below Full E2E Test (reference).
Async Boundary Rule (mandatory)
run-task.py is asynchronous orchestration.
After a successful nohup launch, the correct behavior is:
1. Send a short launch acknowledgment (PID/log/session) 2. Stop this turn immediately 3. Continue only when wake/completion event arrives in the same session
Do not keep waiting in the same turn for Codex completion. Do not poll and then summarize in the same turn unless user explicitly asked for active live monitoring.
Anti-pattern:
run-task.py and keep responding as if completion should appear in this turnCorrect pattern:
run-task.py β acknowledge launch β stop β wait for wakeLaunch Confirmation Gate (mandatory)
Never claim "launched" until you have positive launch proof.
Required proof checklist:
1. nohup command returned a PID
2. process is alive (ps -p )
3. run log contains π§ Starting OpenAI Codex... or equivalent startup marker
4. routing was validated (--validate-only) for Telegram thread runs
If launch fails with β Invalid routing:
sessions_listPre-launch planning note (mandatory)
Before launching Codex, post a short plan in chat:
If staged: explicitly say this run is "phase 1" and what signal decides phase 2.
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, then launch with it.
sessions_list or runtime contextagent:main:main:: β use it directly in --session--session from chat_id / sender heuristicsRules
--session "agent:main:main::" for thread tasksagent:main:telegram:user: for thread tasksβ Invalid routing--telegram-routing-mode auto--telegram-routing-mode thread-only--telegram-routing-mode allow-non-thread or --allow-main-telegramThis is intentional: abort fast > silent misroute
β οΈ ALWAYS launch via nohup β exec timeout will kill the process otherwise.
β οΈ NEVER put the task text directly in the shell command β save the prompt to a file first, then use $(cat file).
# Step 1: Save prompt to a temp file
write /tmp/codex-prompt.txt with your task textStep 2: Launch with $(cat ...)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:whatsapp:group:" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
Telegram (thread-safe default)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
> Do NOT use agent:main:telegram:user: for thread tests/runs.
Telegram Threaded Mode (1:1 DM with threads)
When OpenClaw is used in Telegram threaded mode, each thread has its own session key like agent:main:main:thread:369520 or agent:main:main:topic:369520.
Fail-safe routing (NEW): run-task.py now enforces strict thread routing.
--session contains :thread: or :topic:, the script refuses to start unless Telegram target + thread session UUID are resolved.sessions_list when possible.~/.openclaw/agents/main/sessions/*-topic-.jsonl .--notify-session-id mismatches the session key, it exits with error.Use --notify-session-id to wake the exact thread session:
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::369520" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
All 5 notification types route to the DM thread when --session key contains :thread: or :topic: β
--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 Codex 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)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
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) β
βββββββββββββββ ββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββ
β Codex β
β codex exec β
ββββββββ¬ββββββββ
β
βββββββββββββΌββββββββββββ
βΌ βΌ βΌ
Every 60s On complete On error/timeout
ββββββββββ ββββββββββββ ββββββββββββββββ
β β³ ping β β β
result β β β/β°/π₯ errorβ
β silent β β channel β β channel β
ββββββββββ ββββββββββββ ββββββββββββββββ
WhatsApp notification flow
1. Heartbeat pings every 60s β WhatsApp direct
2. Final result β WhatsApp direct + sessions_send
3. Agent receives completion payload β processes it β sends summary
Iterative continuation mode (wake behavior)
--completion-mode is optional and acts as a hint:
single = one run β continuation summary β stopiterate = continuation summary + exactly one next iteration when gaps remainWake payload frames continuation as the same ongoing OpenClaw conversation after Codex replies to the previous launch.
In iterate mode:
Deterministic wake guard (anti-duplicate)
run_id and wake_idrun-task.py keeps per-project state in /tmp/codex-orchestrator-state-.json No silent launch policy (always-on)
[TRACE][AGENT][WAKE_RECEIVED] ...
- [TRACE][AGENT][DECISION] continue|stop ...
Telegram notification flow (DM Threaded Mode β full pipeline)
1. π Launch notification β thread β (silent; HTML; for prompt; via send_telegram_direct; includes Resume: )
2. β³ Heartbeat (every 60s) β thread β
(silent; plain text; via send_telegram_direct)
3. π‘ Codex mid-task updates β thread β
(on-disk Python script /tmp/codex-notify-{pid}.py; Codex calls file; prefix "π‘ π’ Codex: " auto-added)
4. β
/β/β°/π₯ Result notification β thread β
(HTML; 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 Codex 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.
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:
message tool's threadId parameter is Discord-specific β ignored for Telegram"chatId:topic:threadId" is rejected by the message tool's target resolversend_telegram_direct() bypasses the message tool entirely; calls api.telegram.org directly with message_thread_idDM Threaded Mode (bot-user private chat with threads):
send_telegram_direct(chat_id, text, thread_id=..., parse_mode=...) β
thread_id auto-extracted from session key *:thread: or *:topic: by extract_thread_id()parse_mode="HTML" with for prompt/resultparse_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 arrivereplyTo trap: combining replyTo + message_thread_id can cause Telegram to reject the request or route incorrectlyopenclaw agent --session-id --deliver publishes the wake turn to chat so the user sees the same ongoing assistant conversationForum Groups (supergroup with Forum topics enabled):
send_telegram_direct() approach works; message_thread_id is standard Bot API for Forum topics*:thread: or *:topic:Codex mid-task updates:
run-task.py writes /tmp/codex-notify-{pid}.py to disk before launching Codex[Automation context: ... python3 /tmp/codex-notify-{pid}.py 'msg' ...]"π‘ π’ Codex: " to all messages; cleaned up in finally blockReliability Features
Timeout (default 2 hours)
--timeout 7200 β after 7200s: SIGTERM β wait 10s β SIGKILLCrash safety
try/except wraps entire main β crash notification always sentPID tracking
skills/codex-cli-task/pids/ls skills/codex-cli-task/pids/Silent mode (Telegram only)
Telegram supports silent notifications (no sound).Current policy: all Codex notifications are silent in Telegram:
silent=Truesilent=Trueπ‘ π’ Codex) β silent=Truesilent=Truesilent=TrueWhatsApp does NOT support silent mode β the flag is ignored for WhatsApp.
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 |
| Codex mid-task update | π‘ | β | /tmp/codex-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 |
Codex CLI Flags
exec "task" β non-interactive runresume "task" β continue a previous Codex session--dangerously-bypass-approvals-and-sandbox β no confirmation prompts--json --output-last-message β real-time activity tracking + final output capture--full-auto β optional safer automation modeWhy NOT exec/pty?
exec has 2 min default timeout β kills long taskspty:true, output has escape codes, hard to parsenohup + detached runner: clean, detached, reliableGit requirement
Codex 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:
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 update appears (via /tmp/codex-notify-)
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
Canonical full test prompt pattern
sleep 70) to trigger wrapper heartbeatInteractive test rule (time budget)
For iterate-mode testing, do exactly one continuation step after phase 1.
Reason: validates the iterative path without turning a routine test into a long multi-hop run.
Visibility rule (mandatory)
Between β
OpenAI Codex completed and any next π OpenAI Codex started, there must be a user-facing analysis message in the thread.
Canonical launch (minimal mode)
cat > /tmp/codex-full-test-prompt.txt << 'EOF'
1) notify script now
2) create test file
3) sleep 70 + notify again
4) run several shell commands
5) return short structured report
EOFpython3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-full-test-prompt.txt)" \
--project /tmp/codex-e2e-project \
--session "agent:main:main::" \
--validate-only
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-full-test-prompt.txt)" \
--project /tmp/codex-e2e-project \
--session "agent:main:main::" \
--timeout 900 \
> /tmp/codex-full-test.log 2>&1 &
Verification artifacts
/tmp/codex-full-test.log/tmp/codex-YYYYMMDD-HHMMSS.txt~/.openclaw/codex_sessions.jsonLong-running task guidance
If a Codex task is expected to run longer than ~1 minute, explicitly ask Codex to send intermediate progress updates during execution.
Recommended wording:
For Telegram thread-safe runs, updates should use the injected automation script (/tmp/codex-notify-).
Canonical launch (minimal mode)
cat > /tmp/codex-full-test-prompt.txt << 'EOF'
~10 lines
1) use notify helper now
2) create a test artifact
3) sleep 70 + notify again
4) run several shell commands
5) return short structured report
EOFpython3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-full-test-prompt.txt)" \
--project /tmp/codex-e2e-project \
--session "agent:main:main::" \
--validate-only
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-full-test-prompt.txt)" \
--project /tmp/codex-e2e-project \
--session "agent:main:main::" \
--timeout 900 \
> /tmp/codex-full-test.log 2>&1 &
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:120363425246977860@g.us" \
> /tmp/codex-run.log 2>&1 &
Telegram: Research codebase (thread-safe)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 1800 \
> /tmp/codex-run.log 2>&1 &
Telegram Threaded Mode: Mid-task updates from Codex
run-task.py automatically creates an on-disk notification script before launching Codex, so Codex can send progress updates without seeing bot tokens in the prompt.
cat > /tmp/codex-prompt.txt << 'EOF'
STEP 1: Write analysis to /tmp/report.txt.After step 1, send a progress notification using the script from the
automation context above.
STEP 2: Write summary to /tmp/summary.txt.
EOF
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 1800 \
> /tmp/codex-run.log 2>&1 &
> Never embed bot tokens or raw curl commands in the task prompt.
> Quick reference: launching from a Telegram DM thread (minimal mode) >
> python3 {baseDir}/run-task.py \
> --task "probe" \
> --project ~/projects/x \
> --session "agent:main:main::" \
> --validate-only
>
> nohup python3 {baseDir}/run-task.py \
> --task "$(cat /tmp/prompt.txt)" \
> --project ~/projects/x \
> --session "agent:main:main::" \
> --timeout 900 \
> > /tmp/codex-run.log 2>&1 &
>
> - Required: --task, --project, --session
> - THREAD_ID is auto-extracted from session key
> - target + session UUID are auto-resolved when possible
> - if routing is inconsistent/unresolved, script exits with β Invalid routing
> - launch/heartbeat/result notifications stay on the source threadLong 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:120363425246977860@g.us" \
--timeout 3600 \
> /tmp/codex-run.log 2>&1 &
Cost
Session Resumption
Codex sessions can be resumed to continue previous conversations. This is useful for:
Resume ID β Critical Rule
--resume takes the Codex thread_id, not run_id or wake_id.
Correct source:
π Session registered:
That is the value to pass as --resume .
How to Resume
When a task completes, the Codex thread_id is captured and saved to ~/.openclaw/codex_sessions.json.
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "SESSION_KEY" \
--resume \
> /tmp/codex-run.log 2>&1 &
Session Labels
Use --session-label to give sessions human-readable names for easier tracking.
Listing Recent Sessions
from session_registry import list_recent_sessions, find_session_by_labelrecent = list_recent_sessions(hours=72)
for session in recent:
print(f"{session['thread_id']}: {session['label']} ({session['status']})")
Or manually inspect:
cat ~/.openclaw/codex_sessions.json
When to Resume vs Start Fresh
Resume when:
Start fresh when:
Resume Failure Handling
If a thread_id is invalid or expired:
/tmp/codex-run.logCommon resume failures:
Example Workflow
Step 1: Initial research
write /tmp/research-prompt.txt with "Research the codebase architecture for project X"nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/research-prompt.txt)" \
--project ~/projects/project-x \
--session "agent:main:main::" \
--session-label "Project X architecture research" \
> /tmp/codex-run.log 2>&1 &
Step 2: Find thread_id
tail /tmp/codex-run.log
cat ~/.openclaw/codex_sessions.json | grep "Project X"
Step 3: Follow-up implementation
write /tmp/implement-prompt.txt with "Based on your research, implement the authentication module"nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/implement-prompt.txt)" \
--project ~/projects/project-x \
--session "SESSION_KEY" \
--resume \
--session-label "Project X auth implementation" \
> /tmp/codex-run2.log 2>&1 &
Wake Troubleshooting
When the agent wake / continue chain fails:
sessions_send is enabledall--validate-only/tmp/codex-run.logCommon failure patterns:
Current Stable Behavior
This is the current intended behavior of the Codex adaptation:
thread_id values captured from the JSON event streamCurrent locally validated behavior in this repo:
exec via --jsonresume--output-last-messageπ‘ 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:120363425246977860@g.us" \
> /tmp/codex-run.log 2>&1 &
Telegram: Research codebase (thread-safe)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 1800 \
> /tmp/codex-run.log 2>&1 &
Telegram Threaded Mode: Mid-task updates from Codex
run-task.py automatically creates an on-disk notification script before launching Codex, so Codex can send progress updates without seeing bot tokens in the prompt.
cat > /tmp/codex-prompt.txt << 'EOF'
STEP 1: Write analysis to /tmp/report.txt.After step 1, send a progress notification using the script from the
automation context above.
STEP 2: Write summary to /tmp/summary.txt.
EOF
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 1800 \
> /tmp/codex-run.log 2>&1 &
> Never embed bot tokens or raw curl commands in the task prompt.
> Quick reference: launching from a Telegram DM thread (minimal mode) >
> python3 {baseDir}/run-task.py \
> --task "probe" \
> --project ~/projects/x \
> --session "agent:main:main::" \
> --validate-only
>
> nohup python3 {baseDir}/run-task.py \
> --task "$(cat /tmp/prompt.txt)" \
> --project ~/projects/x \
> --session "agent:main:main::" \
> --timeout 900 \
> > /tmp/codex-run.log 2>&1 &
>
> - Required: --task, --project, --session
> - THREAD_ID is auto-extracted from session key
> - target + session UUID are auto-resolved when possible
> - if routing is inconsistent/unresolved, script exits with β Invalid routing
> - launch/heartbeat/result notifications stay on the source threadLong 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:120363425246977860@g.us" \
--timeout 3600 \
> /tmp/codex-run.log 2>&1 &
π Constraints
--session "agent:main:main::" for thread tasksagent:main:telegram:user: for thread tasksβ Invalid routing--telegram-routing-mode auto--telegram-routing-mode thread-only--telegram-routing-mode allow-non-thread or --allow-main-telegramThis is intentional: abort fast > silent misroute
β οΈ ALWAYS launch via nohup β exec timeout will kill the process otherwise.
β οΈ NEVER put the task text directly in the shell command β save the prompt to a file first, then use $(cat file).
# Step 1: Save prompt to a temp file
write /tmp/codex-prompt.txt with your task textStep 2: Launch with $(cat ...)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:whatsapp:group:" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
Telegram (thread-safe default)
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
> Do NOT use agent:main:telegram:user: for thread tests/runs.
Telegram Threaded Mode (1:1 DM with threads)
When OpenClaw is used in Telegram threaded mode, each thread has its own session key like agent:main:main:thread:369520 or agent:main:main:topic:369520.
Fail-safe routing (NEW): run-task.py now enforces strict thread routing.
--session contains :thread: or :topic:, the script refuses to start unless Telegram target + thread session UUID are resolved.sessions_list when possible.~/.openclaw/agents/main/sessions/*-topic-.jsonl .--notify-session-id mismatches the session key, it exits with error.Use --notify-session-id to wake the exact thread session:
nohup python3 {baseDir}/run-task.py \
--task "$(cat /tmp/codex-prompt.txt)" \
--project ~/projects/my-project \
--session "agent:main:main::369520" \
--timeout 900 \
> /tmp/codex-run.log 2>&1 &
All 5 notification types route to the DM thread when --session key contains :thread: or :topic: β
--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 Codex 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)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.