Skip to content

Claude headless report phase does not preserve the no-tools contract for allowedTools: [] #1580

Description

@Ryo722

Summary

TAKT Phase 2 report generation is defined as a no-tools phase and rejects any provider tool_use / tool_result event.

With the Claude headless provider, however, the explicit no-tools intent represented by:

allowedTools: []
permissionMode: 'readonly'

is not currently preserved as a no-tools Claude Code execution boundary.

This can result in Claude emitting a tool such as Bash during report generation, which TAKT then correctly rejects:

ReportPhaseToolCallError:
Report phase does not allow tool calls, but provider emitted tool "Bash".

I observed this in a downstream workflow using TAKT 0.64.1.

Versions inspected

The relevant execution path was inspected in:

  • TAKT 0.64.1
  • TAKT 0.65.0
  • current main

The report retry tests also pass on the released versions checked:

0.64.1: 46 / 46
0.65.0: 50 / 50

so this does not appear to be caused by missing report retry machinery.

Relevant TAKT behavior

Phase 2 uses an explicit empty tool allowlist.

The fresh-session retry path passes:

allowedTools: []

and OptionsBuilder resolves report/resume execution as readonly with an empty allowlist.

However, the Claude headless adapter currently emits --allowed-tools only for a non-empty list:

if (!isStrictReadonly && options.allowedTools && options.allowedTools.length > 0) {
  args.push('--allowed-tools', options.allowedTools.join(','));
}

Therefore:

allowedTools: []

does not itself produce a Claude CLI no-tools restriction.

In addition, the readonly Phase 2 base options retain the step's resolved MCP server set.

TAKT already has a stricter Claude isolation path via internalAgentIsolation === 'strict-readonly', which applies restrictions including:

--tools ""
--strict-mcp-config
--setting-sources ""
--disable-slash-commands

Ordinary Phase 2 report execution does not currently use that isolation path.

Minimal Claude Code observations

Tested with:

Claude Code 2.1.273
model: Sonnet
permission mode: default

Fresh session without tool restriction

Bash was present in the initialization tool set.

When explicitly instructed to invoke Bash, Claude emitted the tool call and it executed successfully.

Fresh session with --tools ""

Bash was absent from the initialization tool set.

When explicitly instructed to invoke Bash, Claude returned text explaining that Bash was unavailable instead of emitting a Bash tool call.

This suggests --tools "" is relevant to suppressing built-in Claude Code tools in a fresh session.

Resumed session observation

I also resumed a session in which Bash had previously been available while applying:

--tools ""

In that run, Claude still emitted a Bash tool-use attempt, but Claude Code rejected execution:

No such tool available: Bash.
Bash is disabled for this session

I would treat this only as an observed resume-session behavior.

It also makes TAKT's existing fresh-session retry useful: a resumed attempt can fail closed, then retry without depending on that session.

MCP observation

In the fresh --tools "" test, built-in tools such as Bash were removed, but configured MCP tools were still visible.

TAKT's report-phase contract rejects all tool use, not only built-in Claude tools.

TAKT also currently carries the resolved MCP server set into readonly Phase 2 execution.

A complete report-phase no-tools boundary therefore needs to account for both:

  1. Claude Code built-in tools
  2. MCP-provided tools

Expected behavior

An explicit empty tool list should remain semantically distinct through the provider boundary:

allowedTools === undefined
  -> normal/provider-default behavior

allowedTools.length > 0
  -> explicitly constrained tool set

allowedTools.length === 0
  -> explicit no-tools execution

For report generation, the resulting Claude execution context should expose no callable tools to the model.

Actual behavior

For Claude headless report execution, the explicit empty tool list currently does not select an execution boundary that removes all callable tools.

Phase 2 also retains resolved MCP capabilities.

If Claude emits a tool event, TAKT's report guard correctly rejects the attempt.

Suggested direction

I think the existing fail-closed ReportPhaseToolCallError should remain unchanged.

The issue appears to be at the provider capability boundary: Phase 2's explicit no-tools contract needs to map to a Claude execution context that suppresses both built-in and MCP tools.

TAKT already has a stricter Claude isolation mechanism for internal read-only agents, and Phase 3 has an existing precedent for removing tool/MCP access. Reusing or adapting those abstractions for report execution may be preferable to introducing a separate CLI special case.

I would leave the exact implementation to the maintainers because session reuse, MCP configuration, Skills/settings discovery, and provider isolation semantics interact here.

Suggested regression coverage

Provider-level tests could distinguish:

allowedTools === undefined
allowedTools.length > 0
allowedTools.length === 0

and verify that the explicit empty case used by report generation produces no callable built-in or MCP tools.

A report-phase integration test could additionally cover:

  1. Phase 1 Claude execution with normal tools.
  2. A resumed Phase 2 attempt that emits a tool event.
  3. Existing fail-closed detection.
  4. Fresh-session retry with the intended no-tools capability boundary.
  5. Successful report generation without weakening the report guard.

Related

This appears analogous to earlier provider capability-boundary work:

This issue appears to be the Claude headless counterpart of the same general no-tools contract boundary.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions