Skip to content

bug: Headless task returns empty response for skills with context: fork #160

Description

@vybe

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

  1. Create an agent with a skill that has context: fork in its frontmatter
  2. Schedule or manually trigger that skill via headless task execution (UI chat or scheduler)
  3. Observe the execution fails with "Task returned empty response"
  4. 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

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions