# How Subagent Interception Controls Background Task Execution in free-claude-code

> Discover how subagent interception in free-claude-code enforces synchronous Task tool execution by rewriting run_in_background and tracking sub-agent lifecycle.

- Repository: [Ali Khokhar/free-claude-code](https://github.com/Alishahryar1/free-claude-code)
- Tags: internals
- Published: 2026-04-24

---

**Subagent interception in free-claude-code forces synchronous execution of Task tools by rewriting `run_in_background` to `false` at the provider layer while the transcript system tracks sub-agent lifecycle to suppress unrelated output.**

The free-claude-code repository implements a strict subagent interception mechanism to ensure Task tools execute deterministically in the foreground. This system treats every Task tool call as the beginning of a nested conversational flow, requiring precise control over execution timing and UI rendering. By forcing `run_in_background` to `false` regardless of client requests, the architecture guarantees predictable behavior across all provider endpoints.

## The Three-Layer Interception Architecture

Subagent interception operates across three distinct components to guarantee that **Task tools never run in the background**, even when clients explicitly request `run_in_background: true`.

### messaging/transcript.py – Sub-Agent Stack Management

The transcript subsystem in [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py) (lines 226-344) maintains an internal stack to track active sub-agents. When a Task tool is detected, the `_subagent_push()` method (around line 250) pushes the tool's `tool_use_id` onto the `_subagent_stack`. 

While the stack is non-empty, the `_in_subagent()` method returns `True`, causing the transcript renderer to suppress all non-tool content. This ensures the UI displays only the sequential flow of "Task → tool calls → result" without interleaved thinking bubbles or background task indicators. When the tool result arrives, `_subagent_pop()` (line 267) matches the result's ID with the stack top and resumes normal output rendering.

### providers/common/sse_builder.py – SSE Argument Sanitization

Before streaming requests to the LLM, the SSE builder sanitizes tool arguments in `_maybe_fix_background()` (lines 98-116). The code performs an explicit check:

```python
if args_json.get("run_in_background") is not False:
    args_json["run_in_background"] = False

```

This mutation occurs in-place, guaranteeing that the flag is `false` before the provider receives the request. The enforcement happens regardless of the client's original value, ensuring synchronous scheduling through the normal streaming channel.

### providers/openai_compat.py – OpenAI-Compatible Wrapper

The OpenAI-compatible provider wrapper applies identical sanitization at two entry points: the regular tool call path (lines 257-267) and the chunked tool call path (lines 341-350). Both locations execute the same conditional rewrite:

```python
if tool_use["input"].get("run_in_background") is not False:
    tool_use["input"]["run_in_background"] = False

```

This dual-path coverage ensures consistency whether the request arrives as a standard API call or via streaming chunks.

## Execution Flow of a Task Tool

When a Task tool initiates a sub-agent, the system processes the request through six deterministic stages:

1. **Client submits Task tool** – The request includes arguments such as `{"run_in_background": true, "prompt": "..."}`.

2. **Transcript detects sub-agent** – [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py) calls `_subagent_push()` to stack the Task's `tool_use_id`, activating sub-agent mode.

3. **SSE builder sanitizes arguments** – The request passes through [`providers/common/sse_builder.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/common/sse_builder.py), where `_maybe_fix_background()` rewrites the flag to `false`.

4. **OpenAI wrapper confirms sanitization** – [`providers/openai_compat.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/openai_compat.py) performs the same rewrite for both regular and chunked paths (lines 257-267 and 341-350).

5. **Provider executes synchronously** – With `run_in_background` forced to `false`, the provider schedules the task in the foreground and returns results via the standard streaming channel.

6. **Stack cleanup** – Upon receiving the tool result, `_subagent_pop()` removes the task from the stack, allowing the transcript renderer to resume normal output.

## Code Implementation Details

The sub-agent stack management in [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py) handles Task identification and lifecycle tracking:

```python

# messaging/transcript.py – pushing a sub-agent

def _subagent_push(self, tool_id: str, seg: SubagentSegment) -> None:
    tool_id = str(tool_id or "").strip() or f"__task_{len(self._subagent_stack) + 1}"
    self._subagent_stack.append(tool_id)
    self._subagent_segments.append(seg)

```

Provider-layer enforcement occurs in the SSE builder's sanitization logic:

```python

# providers/common/sse_builder.py – sanitising run_in_background

if args_json.get("run_in_background") is not False:
    args_json["run_in_background"] = False

```

The OpenAI-compatible provider implements identical checks for cross-endpoint consistency:

```python

# providers/openai_compat.py – same rewrite for OpenAI-compatible endpoint

if tool_use["input"].get("run_in_background") is not False:
    tool_use["input"]["run_in_background"] = False

```

## Why Background Execution Must Be Disabled

Forcing synchronous execution provides three critical guarantees:

- **Predictable UI rendering** – Users see ordered transcripts without interleaved "thinking" bubbles that would appear for background tasks, as the transcript renderer filters non-tool content while `_in_subagent()` returns `True`.
- **Resource management** – Background workers defined in [`messaging/limiter.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/limiter.py) are never spawned for sub-agents, preventing stray async tasks from lingering after sub-agent completion.
- **Security consistency** – The rewrite prevents malicious clients from hiding long-running work in background threads, ensuring rate limits and logging apply to all Task tool executions.

## Verification and Testing

The interception logic is verified through targeted test suites:

- [`tests/providers/test_subagent_interception.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/tests/providers/test_subagent_interception.py) (lines 30-48) asserts that intercepted arguments contain `run_in_background=False`.
- [`tests/providers/test_streaming_errors.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/tests/providers/test_streaming_errors.py) (lines 424-441) confirms the provider forces the flag to `false` even when clients request background execution.
- [`tests/messaging/test_handler_format.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/tests/messaging/test_handler_format.py) (lines 108-110) validates that the rendered transcript orders sub-agent markers correctly.

## Summary

- Subagent interception forces **synchronous execution** of Task tools by rewriting `run_in_background` to `false` at the provider layer.
- The **transcript stack** in [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py) tracks active sub-agents to suppress non-tool UI output during Task execution.
- Sanitization occurs in both [`providers/common/sse_builder.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/common/sse_builder.py) and [`providers/openai_compat.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/openai_compat.py) to cover all API entry points.
- This architecture ensures **predictable rendering**, prevents resource leaks, and maintains security by blocking background execution attempts.

## Frequently Asked Questions

### What is subagent interception in free-claude-code?

Subagent interception is the mechanism that detects Task tool calls and forces them to execute synchronously by rewriting the `run_in_background` argument to `false`. According to the free-claude-code source code, this ensures nested conversational flows remain deterministic and UI output stays clean while the sub-agent is active.

### How does the transcript system know when to suppress output?

The transcript subsystem uses an internal stack (`_subagent_stack`) managed by `_subagent_push()` and `_subagent_pop()` in [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py). When `_in_subagent()` returns `True` because the stack is non-empty, the renderer filters out non-tool content, showing only the Task invocation and its results.

### Can clients bypass the background execution block?

No. The rewrite happens at the provider layer in [`providers/common/sse_builder.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/common/sse_builder.py) and [`providers/openai_compat.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/openai_compat.py) before requests reach the execution engine. Because the code checks `if args_json.get("run_in_background") is not False` and forces the value to `false`, clients cannot override this behavior regardless of their initial payload.

### Which components handle the `run_in_background` flag rewrite?

Three components enforce this behavior: [`messaging/transcript.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/messaging/transcript.py) manages the sub-agent lifecycle and UI suppression, [`providers/common/sse_builder.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/common/sse_builder.py) sanitizes SSE arguments, and [`providers/openai_compat.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/providers/openai_compat.py) applies identical rewrites for both regular and chunked OpenAI-compatible endpoints.