Summary
Scheduled and manual headless tasks that invoke skills with context: fork in their frontmatter consistently fail with "Task returned empty response". The skill's forked context does its work but the parent Claude Code process exits with code 0 and no text in response_parts, triggering the empty response check. This has caused 8 consecutive daily failures for a specific agent since March 17.
Component
Agent Runtime / agent_server/services/claude_code.py (headless task execution)
Priority
P2 — Scheduled skill invocations silently fail daily; workaround is to invoke via MCP chat instead of headless task.
Error
[TaskExecService] Failed to execute task on agent: Task returned empty response
Agent container returns HTTP 500:
{"detail": "Task returned empty response"}
Execution records show: claude_session_id: null, cost: null, context_used: null — indicating the stream-json output from Claude Code contained no result message with text content.
Location
- File:
docker/base-image/agent_server/services/claude_code.py
- Line: ~996-999
- Function:
execute_headless_task()
Root Cause
When a headless task message triggers a skill with context: fork (e.g., /synthesize-learnings), Claude Code forks the conversation context to a sub-process. The forked process may do all the work (file reads, writes, tool calls), but the parent process that Trinity is watching via --output-format stream-json exits with code 0 and an empty result field. The current code treats any empty response_parts as a hard failure:
response_text = "\n".join(response_parts) if response_parts else ""
response_text = sanitize_text(response_text)
if not response_text:
raise HTTPException(
status_code=500,
detail="Task returned empty response"
)
This check doesn't account for context: fork skills where the meaningful work happens in the forked context, not the parent stream.
Timeline: Last successful execution was March 16. First failure March 17. Commits c6af307 (default model to sonnet for subscription compatibility) and e47a9f8 (create execution records for all /api/chat calls) landed between the last success and first failure, possibly changing how model selection or execution tracking interacts with forked contexts.
Reproduction Steps
- Create an agent with a skill that has
context: fork in its frontmatter
- Schedule or manually trigger that skill via headless task execution (UI chat or scheduler)
- Observe the execution fails with "Task returned empty response"
- Note: invoking the same skill via MCP
chat_with_agent works fine (different code path)
Evidence
- 8 consecutive daily scheduled failures (March 17-24), same agent, same skill
- All failures:
claude_session_id: null, cost: null — no Claude session created
- Same agent's other schedule (
/heartbeat-procedure, no context: fork) succeeds every hour
- Same agent responds normally to MCP chat and direct task messages
- Duration varies (6s to 910s) suggesting Claude Code does start but produces no captured output
Suggested Fix
Option A — Don't treat empty response as fatal when the process exited cleanly:
if not response_text and return_code == 0:
# Skill may have used context: fork — work happened in sub-process
response_text = f"Task completed (exit 0, no direct output — skill may use context: fork)"
logger.info(f"[Headless Task] Task {task_session_id} exited cleanly but with no response text")
Option B — Capture the forked context output and include it in response_parts.
Option C — Check the execution_log for tool_use entries: if tools were called but no text was emitted, treat as success rather than failure.
Additional Context
Also noted: 'DatabaseManager' object has no attribute 'mark_execution_dispatched' warning fires on every execution (all agents). This is a missing method in the DatabaseManager class — non-blocking but means execution dispatch tracking is incomplete.
Environment
- Trinity version:
003335b
- Docker version: 28.3.3
- OS: Debian GNU/Linux 12 (bookworm)
- Claude Code version: 2.1.78
Related
docker/base-image/agent_server/services/claude_code.py — lines 992-999
src/backend/services/task_execution_service.py — execution lifecycle
- Commit
c6af307 — default model to sonnet (landed between last success and first failure)
- Commit
e47a9f8 — create execution records for all /api/chat calls
Summary
Scheduled and manual headless tasks that invoke skills with
context: forkin their frontmatter consistently fail with "Task returned empty response". The skill's forked context does its work but the parent Claude Code process exits with code 0 and no text inresponse_parts, triggering the empty response check. This has caused 8 consecutive daily failures for a specific agent since March 17.Component
Agent Runtime /
agent_server/services/claude_code.py(headless task execution)Priority
P2 — Scheduled skill invocations silently fail daily; workaround is to invoke via MCP chat instead of headless task.
Error
Agent container returns HTTP 500:
{"detail": "Task returned empty response"}Execution records show:
claude_session_id: null,cost: null,context_used: null— indicating the stream-json output from Claude Code contained noresultmessage with text content.Location
docker/base-image/agent_server/services/claude_code.pyexecute_headless_task()Root Cause
When a headless task message triggers a skill with
context: fork(e.g.,/synthesize-learnings), Claude Code forks the conversation context to a sub-process. The forked process may do all the work (file reads, writes, tool calls), but the parent process that Trinity is watching via--output-format stream-jsonexits with code 0 and an emptyresultfield. The current code treats any emptyresponse_partsas a hard failure:This check doesn't account for
context: forkskills where the meaningful work happens in the forked context, not the parent stream.Timeline: Last successful execution was March 16. First failure March 17. Commits
c6af307(default model to sonnet for subscription compatibility) ande47a9f8(create execution records for all /api/chat calls) landed between the last success and first failure, possibly changing how model selection or execution tracking interacts with forked contexts.Reproduction Steps
context: forkin its frontmatterchat_with_agentworks fine (different code path)Evidence
claude_session_id: null,cost: null— no Claude session created/heartbeat-procedure, nocontext: fork) succeeds every hourSuggested Fix
Option A — Don't treat empty response as fatal when the process exited cleanly:
Option B — Capture the forked context output and include it in
response_parts.Option C — Check the execution_log for tool_use entries: if tools were called but no text was emitted, treat as success rather than failure.
Additional Context
Also noted:
'DatabaseManager' object has no attribute 'mark_execution_dispatched'warning fires on every execution (all agents). This is a missing method in the DatabaseManager class — non-blocking but means execution dispatch tracking is incomplete.Environment
003335bRelated
docker/base-image/agent_server/services/claude_code.py— lines 992-999src/backend/services/task_execution_service.py— execution lifecyclec6af307— default model to sonnet (landed between last success and first failure)e47a9f8— create execution records for all /api/chat calls