Skip to content

fix(claude_code): echoed system prompt false-matches idle, blocking workspace-trust dialog#319

Merged
haofeif merged 1 commit into
awslabs:mainfrom
cdrury526:fix/claude-code-trust-dialog-false-idle
Jun 27, 2026
Merged

fix(claude_code): echoed system prompt false-matches idle, blocking workspace-trust dialog#319
haofeif merged 1 commit into
awslabs:mainfrom
cdrury526:fix/claude-code-trust-dialog-false-idle

Conversation

@cdrury526

Copy link
Copy Markdown
Contributor

Summary

cao launch --provider claude_code fails to initialize in any working directory that Claude Code has not yet trusted. The session is created, Claude starts, but CAO kills it after 30s with:

Claude Code initialization timed out after 30 seconds

Reproduction

  1. Point CAO at a fresh directory Claude Code hasn't seen before.
  2. cao launch --agents code_supervisor --provider claude_code --headless ...
  3. The tmux session spawns, Claude shows the "Is this a project you created or one you trust?" dialog, and CAO never accepts it → init times out → session killed.

(Pre-trusting the directory by launching claude there once and accepting the dialog works around it, which is what pointed to the root cause.)

Root cause

_handle_startup_prompts() uses IDLE_PROMPT_PATTERN = r"[>❯][\s\xa0]" as an early-return "Claude is ready" signal. The supervisor system prompt injected via --append-system-prompt contains a line that starts with > `memory_store` (the memory-tools note). When the shell echoes the long launch command into the capture buffer, that > appears ~300ms after launch — well before the trust dialog renders.

So the handler matches the echoed > , logs Claude Code idle prompt detected, no prompts needed, and returns before the trust dialog exists. initialize() then waits for {IDLE, COMPLETED}, but the trust dialog reads as WAITING_USER_ANSWER, so it never becomes idle → 30s timeout → terminal_service tears the session down.

Observed in the server log:

21:53:46,369  Claude Code idle prompt detected, no prompts needed   # 358ms after send — the echoed "> memory_store"
21:54:16,382  wait_until_status: timeout waiting for {completed, idle}
21:54:16,382  Failed to create terminal: Claude Code initialization timed out after 30 seconds

Fix

Remove the bare IDLE_PROMPT_PATTERN early-return. The version banner (Welcome to / Claude Code v\d+) is the only "ready" signal that cannot appear in the echoed launch command, so it's kept; the trust and bypass dialogs are still handled explicitly above it; and wait_until_status() remains the real readiness gate. Note that anchoring the pattern to start-of-line would not help here, because the offending > is itself at the start of a line in the injected prompt.

After the fix, the same launch in a fresh untrusted dir logs:

Workspace trust prompt detected, auto-accepting
status changed: idle -> processing

…and the worker runs to completion.

Test

Added a regression test (test_echoed_prompt_does_not_short_circuit_trust) that returns the echoed command (with the > memory_store marker, no dialog) on the first poll and the trust dialog on the second, asserting the dialog is accepted via Enter. This fails on the old early-return and passes now. Full provider suite (114 tests) passes; black/isort clean.

Environment

  • CAO 2.2.0 (current main)
  • Claude Code 2.1.183

Happy to file a tracking issue per CONTRIBUTING if preferred.

🤖 Generated with Claude Code

…st dialog

The startup handler treated any "> "/"❯ " in the capture buffer as the idle
prompt and returned early. The injected --append-system-prompt contains a line
starting with "> `memory_store`", which the shell echoes into the capture
buffer ~300ms before the workspace-trust dialog renders. The handler matched
that marker, declared Claude Code ready, and returned before accepting the
trust dialog. initialize() then blocked on {IDLE, COMPLETED} for 30s and the
session was killed, so `cao launch` failed on every not-yet-trusted working
directory.

Remove the bare IDLE_PROMPT_PATTERN early-return. The version banner
("Welcome to" / "Claude Code v<n>") is the only ready signal that cannot appear
in the echoed launch command; the trust and bypass dialogs are still handled
explicitly above, and wait_until_status() remains the real readiness gate.

Add a regression test that returns the echoed command on the first poll and the
trust dialog on the second, asserting the trust dialog is accepted (the old
early-return would skip it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@bc4d837). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #319   +/-   ##
=======================================
  Coverage        ?   87.22%           
=======================================
  Files           ?       92           
  Lines           ?    10970           
  Branches        ?        0           
=======================================
  Hits            ?     9569           
  Misses          ?     1401           
  Partials        ?        0           
Flag Coverage Δ
unittests 87.22% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Claude Code provider initialization in untrusted workspaces by preventing _handle_startup_prompts() from short-circuiting on an echoed "> " sequence from the launch command, so the workspace-trust dialog can be auto-accepted and initialization can proceed to the real readiness gate (wait_until_status).

Changes:

  • Removed the IDLE_PROMPT_PATTERN early-return from Claude Code startup prompt handling, keeping only the version banner as the “no prompts needed” signal.
  • Added a regression test that simulates an echoed launch command frame followed by the trust dialog, asserting the trust dialog is accepted.
  • Updated test module documentation to reflect the new coverage target.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/cli_agent_orchestrator/providers/claude_code.py Removes the idle-prompt early return and documents the echoed-command false-positive scenario; keeps version-banner early return.
test/providers/test_claude_code_coverage.py Replaces the old idle-prompt early-return test with a regression test for echoed-command vs trust-dialog handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 336 to 337
if re.search(r"Welcome to|Claude Code v\d+", clean_output):
logger.info("Claude Code started without prompts")
@haofeif

haofeif commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@cdrury526 thanks for your PR.

Findings

P2 - Copilot keeps URL access even when web_fetch is not allowed

src/cli_agent_orchestrator/providers/copilot_cli.py:140 launches Copilot with:

command_parts = ["copilot", "--allow-all"]

The PR intentionally leaves web_fetch unmapped for Copilot in src/cli_agent_orchestrator/utils/tool_mapping.py:44-50, so restricted roles only get --deny-tool shell / --deny-tool write style flags. They do not get any URL-deny behavior.

That conflicts with docs/tool-restrictions.md:47, which now says reviewer is read-only with "no writes, execution, or network".

External confirmation:

  • GitHub's Copilot CLI command reference says --allow-all is equivalent to --allow-all-tools --allow-all-paths --allow-all-urls.
  • The same reference documents --deny-tool and --deny-url as separate controls, and says --deny-url denies access to URLs/domains.
  • The local installed Copilot CLI help matches this: --allow-all enables all permissions including URLs, while --deny-tool only controls tools.

Impact:

A Copilot reviewer/supervisor launched by CAO still has all URL access pre-approved because CAO passes --allow-all and never emits a corresponding URL restriction when web_fetch is absent. This makes the updated "no network" claim false for Copilot.

Suggested fixes:

  • Wire web_fetch to Copilot's URL-permission flags, for example by only granting --allow-all-urls/URL allow rules when web_fetch is present, or by adding an explicit URL deny when it is absent.
  • If Copilot URL gating cannot be expressed safely, document Copilot as a provider limitation instead of listing it as fully hard-enforced for network egress.

P3 - OpenCode ignores the new web_fetch grant

OpenCode has first-class webfetch and websearch permissions, but CAO's OpenCode translator does not map web_fetch:

_CAO_CATEGORY_MAP = {
    "execute_bash": ["bash"],
    "fs_read": ["read"],
    "fs_write": ["edit", "write"],
    "fs_list": ["glob", "grep"],
    "fs_*": ["read", "edit", "write", "glob", "grep"],
}

_HARDCODED_DENY = frozenset(["task", "question", "webfetch", "websearch", "codesearch"])

I verified the effective behavior:

cao_tools_to_opencode_permission(
  ["@builtin", "fs_*", "execute_bash", "web_fetch", "@cao-mcp-server"]
)
=> webfetch=deny, websearch=deny

External confirmation:

  • OpenCode's public permissions docs say permission controls whether an action is allow, ask, or deny.
  • The available permissions include webfetch for fetching URLs and websearch for web search.
  • OpenCode's tools docs also show webfetch and websearch controlled through the permission field.

Impact:

role: developer now includes web_fetch, but an OpenCode developer installed through CAO still cannot use OpenCode's web tools unless the profile uses allowedTools: ["*"]. This is a lower-severity behavior/docs mismatch, but it should either be fixed or explicitly documented.

Suggested fix:

Map web_fetch to OpenCode's webfetch and websearch permissions and add an OpenCode translator test. If codesearch is intentionally separate, leave it denied and document that distinction.

P3 - Soft-enforcement warning skips allowedTools: []

src/cli_agent_orchestrator/services/terminal_service.py:249 checks:

if provider in SOFT_ENFORCEMENT_PROVIDERS and allowed_tools and "*" not in allowed_tools:

resolve_allowed_tools() treats an explicit empty list as a real profile override because it branches on profile_allowed_tools is not None. That means allowedTools: [] is a restricted policy, but the warning is skipped because [] is falsy.

Suggested fix:

Use allowed_tools is not None and "*" not in allowed_tools so empty explicit allowlists still trigger the warning.

Confirmed Good

  • Claude Code mapping now blocks WebFetch and WebSearch when web_fetch is absent.
  • Gemini mapping uses the correct native tool names: web_fetch and google_web_search. This is confirmed by the installed Gemini CLI bundle and public CLI help for policy support.
  • Kiro/Q agent validation accepted an allowedTools list containing web_fetch, so I do not have a current finding there.

Checks Run

git diff --check origin/main...pr-311
uv run pytest test/utils/test_tool_mapping.py test/cli/commands/test_install_opencode.py test/models/test_opencode_agent.py -q
uv run pytest test/services/test_terminal_service_full.py test/services/test_terminal_service_coverage.py test/mcp_server/test_resolve_child_allowed_tools.py test/cli/commands/test_launch.py -q
uv run pytest test/providers/test_copilot_cli_unit.py test/providers/test_gemini_cli_unit.py test/providers/test_claude_code_unit.py -q

All passed.

@haofeif haofeif added the bug Something isn't working label Jun 23, 2026

@haofeif haofeif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cdrury526 thanks for the great contribution !

@haofeif haofeif merged commit 462fa2f into awslabs:main Jun 27, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants