Skip to content

2.0.6 — remove copilot_cli backend + upstream Geny CLI compat patches#208

Merged
CocoRoF merged 1 commit into
mainfrom
refactor/remove-copilot-and-upstream-patches
May 20, 2026
Merged

2.0.6 — remove copilot_cli backend + upstream Geny CLI compat patches#208
CocoRoF merged 1 commit into
mainfrom
refactor/remove-copilot-and-upstream-patches

Conversation

@CocoRoF
Copy link
Copy Markdown
Owner

@CocoRoF CocoRoF commented May 20, 2026

Two coordinated changes, bumping the version to 2.0.6.

Removed: `copilot_cli` provider

The `gh copilot` CLI advertises (and the `CopilotCLIClient` honestly mirrored) `supports_streaming=False`, `supports_tools=False`, `supports_mcp_passthrough=False` — it is a one-shot text-in / text-out subprocess that cannot participate in the executor's Stage-10 dispatch or Sub-Worker delegation flows. Keeping the backend encouraged consumers to enable a provider that would fail on the first tool call, so it's removed entirely:

  • `llm_client/copilot.py` (the `CopilotCLIClient`) — deleted
  • `llm_client/registry.py` factory + registration
  • `llm_client/init.py` re-exports
  • `llm_client/translators/_cli.py` helpers: `compose_copilot_prompt`, `copilot_argv`, `parse_plain_text_to_response`
  • `core/pipeline.py:_creds_to_client_kwargs` `copilot_cli` branch
  • `tests/_fixtures/fake_gh.py` + `tests/llm_client/{unit,conformance}/test_copilot*.py`

Upstreamed: four Claude-Code CLI compatibility fixes from Geny

The Geny project's `service/llm_patches.py` carried four runtime monkey-patches over 2.0.5's claude_code surface, none of which were specific to Geny. Folded into the executor here:

A. `--verbose` auto-injected with stream-json

Claude Code CLI ≥ 2.1.x exits with `Error: When using --print, --output-format=stream-json requires --verbose` unless `--verbose` is present. `claude_code_argv` now emits it alongside the stream-json switches automatically.

B. `--bare` auto-stripped on the OAuth path

The `--bare` flag explicitly disables OAuth + keychain reads, which crashes every subscription user with `Not logged in · Please run /login`. `claude_code_argv` now checks `ANTHROPIC_API_KEY` and omits `--bare` when no key is reachable, so the same `bare_mode=True` default works for both API-key and OAuth auth transparently.

C. Dropped auto-`--tools ""` emit when MCP is configured

Earlier executor versions disabled the CLI's built-in palette (`Bash` / `Read` / `Write` / `Edit` / …) whenever a host MCP config was attached, on the theory that the LLM would otherwise hallucinate against unknown built-ins. In practice most hosts want both surfaces (e.g. a Sub-Worker writing files via `Write` while delegating to MCP-wrapped host tools). Hosts that want the old MCP-only behaviour can pass `extra_args=("--tools", "")` explicitly. `--strict-mcp-config` still emits.

D. `tool_use` blocks stripped from the assembled `APIResponse`

Claude Code CLI 2.1.x runs the entire agentic loop internally — each intermediate turn arrives as its own `"assistant"` envelope and the accumulator collected every block from every envelope. Host pipelines (Geny's Stage 10 etc.) treated those as pending and tried to re-dispatch them against their own tool registries, producing instant `ERROR (0 ms) — No output` ghost failures for every CLI tool call.

The Phase-I design contract is explicit:

Stage 10 receives that assistant message, sees no `tool_use` blocks (they were executed inside the CLI), and naturally no-ops.

So we strip the blocks at the accumulator boundary (`StreamJsonAccumulator.finalize` + `parse_json_output_to_response`). `stop_reason` is preserved verbatim so callers can still distinguish `end_turn` from `tool_use` for telemetry / retry decisions; hosts that want the raw per-block records can still get them from the `feed()` event stream.

Test results

3127 passed, 8 skipped, 0 failed (1.66 s for claude_code-only subset; 28.9 s for the full suite excluding integration).

Test updates:

  • `test_argv_non_stream_uses_json_output` / `test_argv_carries_bare_and_workspace`: monkeypatch `ANTHROPIC_API_KEY` to keep exercising the API-key path
  • `test_argv_stream_uses_stream_json_io_with_verbose`: assert new auto-`--verbose`
  • `test_argv_bare_stripped_on_oauth_path`: new test for the OAuth-path strip
  • `test_argv_host_mcp_emits_strict_and_keeps_builtins` / `test_argv_host_mcp_with_explicit_allow_tools_emits_allowedtools`: rewritten — built-ins stay, only `--strict-mcp-config` emits
  • `test_parse_json_output_drops_tool_use_blocks` / `test_assemble_drops_tool_use_blocks` / `test_send_oneshot_tool_use_blocks_dropped` / `test_tool_use_blocks_dropped`: rewritten to assert `resp.tool_calls == []` while `stop_reason` is preserved

Companion changes downstream

The Geny project lands a matching PR (CocoRoF/Geny#827) that:

  • removes its frontend / backend copilot wiring
  • on a follow-up PR, bumps the executor pin from `>=2.0.5` to `>=2.0.6` and slims down `service/llm_patches.py` to only the residual Geny-specific bits (session-logger `ContextVar` observability tap)

🤖 Generated with Claude Code

…patches

Two coordinated changes, bumping the version to 2.0.6.

== Removed: ``copilot_cli`` provider ==

The ``gh copilot`` CLI advertises (and the ``CopilotCLIClient`` honestly
mirrored) ``supports_streaming=False``, ``supports_tools=False``,
``supports_mcp_passthrough=False`` — it is a one-shot text-in /
text-out subprocess that cannot participate in the executor's
Stage-10 dispatch / Sub-Worker delegation flows. Keeping the backend
encouraged consumers to enable a provider that would fail on the
first tool call, so it's removed entirely:

  - ``llm_client/copilot.py`` (the ``CopilotCLIClient``) — deleted.
  - ``llm_client/registry.py`` ``_copilot_cli_factory`` + registration.
  - ``llm_client/__init__.py`` re-exports of ``CopilotCLIClient``.
  - ``llm_client/translators/_cli.py`` ``compose_copilot_prompt`` /
    ``copilot_argv`` / ``parse_plain_text_to_response`` helpers.
  - ``llm_client/translators/__init__.py`` re-exports of the above.
  - ``core/pipeline.py:_creds_to_client_kwargs`` ``"copilot_cli"``
    branch.
  - ``tests/_fixtures/fake_gh.py`` + ``tests/llm_client/{unit,
    conformance}/test_copilot*.py`` test suites.

== Upstreamed: four Claude-Code CLI compatibility fixes from Geny ==

The Geny project's ``service/llm_patches.py`` carried four runtime
monkey-patches over executor 2.0.5's claude_code surface, none of
which were specific to Geny. They're folded into the executor proper
here so every consumer benefits without dragging a patch layer.

A. ``--verbose`` auto-injected with stream-json. Claude Code CLI ≥
   2.1.x exits with ``Error: When using --print, --output-format=
   stream-json requires --verbose`` unless ``--verbose`` is present.
   ``claude_code_argv`` now emits it alongside the stream-json
   switches automatically.

B. ``--bare`` auto-stripped on the OAuth path. The ``--bare`` flag
   explicitly disables OAuth + keychain reads, which crashes every
   subscription user with ``"Not logged in · Please run /login"``.
   ``claude_code_argv`` now checks for ``ANTHROPIC_API_KEY`` in the
   spawning process's env and omits ``--bare`` when no key is
   reachable, so the same ``bare_mode=True`` default works for both
   API-key and OAuth auth transparently.

C. Dropped the auto-``--tools \"\"`` emit when MCP is configured.
   Earlier executor versions disabled the CLI's built-in palette
   (``Bash`` / ``Read`` / ``Write`` / ``Edit`` / …) whenever a host
   MCP config was attached, on the theory that the LLM would
   otherwise hallucinate against unknown built-ins. In practice
   most hosts want *both* surfaces (e.g. a Sub-Worker writing files
   via ``Write`` while delegating to MCP-wrapped host tools). Hosts
   that want the old MCP-only behaviour can still pass
   ``extra_args=(\"--tools\", \"\")`` explicitly. ``--strict-mcp-config``
   still emits — the MCP surface stays scoped to the host's bridge.

D. ``StreamJsonAccumulator.finalize`` (and ``parse_json_output_to_response``)
   now strip ``tool_use`` content blocks from the assembled
   ``APIResponse``. Claude Code CLI 2.1.x runs the entire agentic
   loop *internally* — each intermediate turn arrives as its own
   ``\"assistant\"`` envelope and the accumulator collected every
   block from every envelope. Host pipelines (Geny's Stage 10
   etc.) treated those as pending and tried to re-dispatch them
   against their own tool registries, producing instant ``ERROR
   (0 ms) — No output`` ghost failures for every CLI tool call.
   The Phase-I design contract is explicit: ``Stage 10 receives
   that assistant message, sees no tool_use blocks (they were
   executed inside the CLI), and naturally no-ops`` — so we strip
   the blocks at the accumulator boundary. ``stop_reason`` is
   preserved verbatim so callers can still distinguish ``end_turn``
   from ``tool_use``; hosts that want the raw per-block records
   can still get them from the ``feed()`` event stream.

== Test updates ==

Adapted the affected claude_code unit + conformance tests to assert
the new semantics:

  - ``test_argv_non_stream_uses_json_output`` / ``test_argv_carries_bare_and_workspace``:
    monkeypatch ``ANTHROPIC_API_KEY`` to keep exercising the API-key
    path where ``--bare`` is expected.
  - ``test_argv_stream_uses_stream_json_io_with_verbose``: assert
    the new ``--verbose`` auto-injection.
  - ``test_argv_bare_stripped_on_oauth_path``: new test for the
    OAuth-path strip.
  - ``test_argv_host_mcp_emits_strict_and_keeps_builtins`` /
    ``test_argv_host_mcp_with_explicit_allow_tools_emits_allowedtools``:
    rewritten — built-ins now stay; only ``--strict-mcp-config`` is
    emitted in the MCP path.
  - ``test_parse_json_output_drops_tool_use_blocks`` /
    ``test_assemble_drops_tool_use_blocks`` /
    ``test_send_oneshot_tool_use_blocks_dropped`` /
    ``test_tool_use_blocks_dropped``: rewritten to assert
    ``resp.tool_calls == []`` while ``stop_reason`` is preserved.

3127 unit + conformance tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CocoRoF CocoRoF merged commit 0842a20 into main May 20, 2026
5 of 6 checks passed
@CocoRoF CocoRoF deleted the refactor/remove-copilot-and-upstream-patches branch May 20, 2026 07:35
CocoRoF added a commit to CocoRoF/Geny that referenced this pull request May 20, 2026
…s.py (#828)

geny-executor 2.0.6 (released today, see CocoRoF/geny-executor#208)
absorbed four of the five compat patches that previously lived in
``service/llm_patches.py``:

  A. ``--verbose`` auto-injected when ``--print --output-format=
     stream-json`` is used (required by Claude Code CLI ≥ 2.1.x).
  B. ``--bare`` auto-stripped when no ``ANTHROPIC_API_KEY`` is in
     env, so the OAuth subscription path no longer crashes.
  C. Dropped the auto-``--tools ""`` emit on MCP-configured spawns —
     CLI built-ins (``Bash`` / ``Read`` / ``Write`` / ``Edit`` / …)
     now stay available alongside MCP-wrapped host tools, which is
     what every host (notably Geny's Sub-Worker) actually wants.
  D. ``StreamJsonAccumulator.finalize`` (and ``parse_json_output_to_response``)
     now drop the ``tool_use`` blocks the CLI dispatched internally,
     so Stage 10 naturally no-ops instead of ghost-erroring against
     the ``mcp__geny__<name>`` ids it has no registration for.

Plus the executor lost the dead ``copilot_cli`` provider entirely
(Geny had already removed its end of that wiring in #827).

This bumps both pins (``pyproject.toml`` + ``requirements.txt``)
from ``>=2.0.5`` to ``>=2.0.6``.

``llm_patches.py`` shrinks from ~830 lines to ~479 lines. Two
patches remain — both are Geny-specific and won't fold upstream
without the executor gaining hooks Geny is the only consumer of:

  1. Friendly Korean error messages for ``is_error`` stream-json
     result envelopes (auth-expired → Settings card hint, generic
     API errors → structured short message). Will fold upstream once
     the executor gains a proper i18n hook.

  2. CLI-tool observability into Geny's :class:`SessionLogger` via
     the new :data:`cli_stream_logger_ctx` ContextVar — taps
     ``StreamJsonAccumulator.feed`` to surface CLI built-in tool
     calls (Bash / Read / Write / Edit / …) into the session log
     alongside the MCP tool entries that ``mcp_bridge_controller``
     already emits. ``mcp__*`` prefixed names are skipped so the
     bridge-side log isn't double-rendered. Will fold upstream
     once the executor emits first-class CLI-tool events on the
     pipeline event bus.

Tests are rewritten end-to-end:

  - Old: 21 tests for ``_patched_argv`` predicate + idempotent
    installer (the patched-argv function is gone).
  - New: 14 focused tests covering:
    - ``_friendly_error_message_for_result_envelope`` (3 cases:
      auth, generic API error, fallback).
    - ``_maybe_extract_error_envelope`` (bytes/str input + 7
      parametrised non-match cases).
    - ``install_llm_patches`` idempotency + re-export coverage.
    - Assembler patch end-to-end: raises Korean friendly error
      on auth-failure envelope; passes through clean streams.
    - Stream observability: emits ``log_tool_use`` for non-MCP
      tool blocks; skips ``mcp__*`` to avoid bridge double-render;
      emits ``log_tool_result`` with duration; handles both
      string and content-block-list result shapes; inert when
      no ContextVar is set.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant