Skip to content

fix: keep interactive input resilient through background errors - #384

Merged
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/cleanup-input-resilience-20261025
Oct 27, 2026
Merged

Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/cleanup-input-resilience-20261025

Conversation

@bkrabach

@bkrabach Brian Krabach (bkrabach) commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Keep interactive input usable when background loop errors occur during initialization, idle input, steering, and cleanup. Add a CLI-owned non-modal diagnostic scope and carry prompt-child KeyboardInterrupt back to the parent for normal exit confirmation.

Candidate head: 59ce58b413ff9582af5ccac49ff8cc3e1cfdf8c5 (original base b7e533a49ab3b155098caa68b4f3c5b7001d0482). Target main: 72a972d27ae9e3bf06c1cd4b80e74a100e2a4511. The exact qualified combined tree is 99bb619e3bf44c721e7b65094e1e9160e2921490.

Why and how

With prompt-toolkit 3.0.53, repeated background exceptions opened modal Enter-acknowledgment prompts: multiple Ctrl-C presses were consumed before the outer exit handler could confirm exit. A prompt-child KeyboardInterrupt could also escape asyncio before the parent caught it.

  • loop_errors.py snapshots bounded safe exception type and source-frame metadata. It withholds arbitrary messages, task/context representations, locals, notes/chains, and traceback source. The fixed _async_httpx_client hint names a known symptom, not a proven root cause.
  • The loop callback only snapshots/enqueues. An observed worker uses prompt-toolkit's public terminal executor. The queue has at most 16 pending records, adjacent-repeat/overflow counting, and output rate limiting. Raw arguments and the private comparison digest are not published.
  • Idle and steering prompts use set_exception_handler=False; steering SIGINT behavior is preserved. Reporting spans initialization through cleanup and restores the previous loop handler only if it still owns the slot.
  • Final draining follows replacement workers under one absolute 500 ms deadline, including the ready-callback race. This permits final diagnostic loss; it does not guarantee delivery or bound a blocked executor thread or later process/executor shutdown.
  • CLIHandoff.prompt carries a child's interrupt as a sentinel, observes both children, and rechecks release priority before raising in the parent. Late release still wins as EOF.

The reporter is imported only when interactive chat is awaited. The real main-module namespace and passive doctor's lazy entry route are preserved.

Diagnostic-policy decision and authorization

Engineering review accepts privacy-bounded type/frame diagnostics without another modal prompt. Arbitrary exception payloads are deliberately withheld; no opt-in raw dump or previous-handler chaining is added. Ordinary verbose error handling is unchanged. No current repository requirement mandates raw messages for all background errors under verbose mode. Handler ownership, bounded overload accounting, and possible final loss are explicit trade-offs, not universal delivery guarantees.

The repository administrator authorized the normal PR-only admin merge path for qualified fixes. This records an engineering decision and merge authorization, not a formal GitHub approval or fabricated maintainer review. The one-approval rule is handled through the supported admin PR merge, with no repository-rule or settings change. The earlier advisory disagreement about draft status is resolved by the independent-stack and current-main qualification below.

Current-main qualification

Main advanced by cc05835d7133938ca9c8cb0c341901bb50daddf5 (compact incomplete-cost warnings) and 72a972d27ae9e3bf06c1cd4b80e74a100e2a4511 (doctor checklist). Only AGENTS.md overlaps the candidate, in separate sections. The clean object-only merge preserves both additions; the candidate feature files remain byte-identical to the terminal-qualified head, while current-main doctor/cost-warning files match main.

The combined noneditable wheel has 280 members and SHA-256 60f308c17e49dae010a2d5345ba94aae9166ea0e367dac96bc595fb2417b08b8. All 473 source-archive entries, installed payloads, and unchanged dependency bytes were checked. Normal-GIL CPython 3.14.7, Linux aarch64, published unpatched Core 2.0.2 and google-genai 2.33.0 were used; no local Core, SDK, or Gemini patch was required.

  • Current feature plus doctor focus: 285 passed, 1 existing xfail.
  • POSIX integration: 13 passed.
  • Installed doctor acceptance: passed, including actual console, module, legacy launchers, and current-main asset verification outside the checkout.
  • PR CI run 40354582717 at candidate head: all 8 jobs passed, plus CLA. This covers Python 3.11/3.12 on Ubuntu, macOS, and Windows, plus both POSIX integration jobs. The declared whole-suite jobs run without test exclusions; the Ubuntu 3.11 installed-wheel doctor step passed. The PR merge-preview tree matches the qualified combined tree.

Full local-suite failures remain disclosed

Earlier original-base evidence was 3,020 passed, 26 failed, 4 skipped, 1 xfailed; all 26 failing IDs reproduced on baseline. The newer offline current-main control was 2,959 passed, 123 failed, 4 skipped, 1 xfailed; the combined tree was 2,990 passed, the exact same 123 failed IDs, 4 skipped, 1 xfailed. All 31 added tests passed, with zero combined-only failing IDs and unchanged relative common-test order. Both full default runs used the existing not integration selection; the 13 integration tests ran separately.

The 26 previous failing IDs are retained within the 123; 97 additional IDs reflect the newer isolated offline environment/cache state. Diagnosis found 110 DNS/clone exit-128 failures, 11 selected-source errors presented through Click's truncated output, one concurrent-write test whose two threads failed on provider-source resolution before reaching atomic write, and one existing Windows-URI expectation. Fresh per-test homes and @main bindings need real remote resolution even with prefetched objects. No fake bindings, isolation changes, origin-guard bypass, filtering, skip changes, or reruns to green were used.

These local full runs failed; they are not presented as a green local suite or a check waiver. The disposition is a documented local-offline qualification limit, supported by zero introduced failure IDs and the already-green actual declared GitHub CI matrix. It does not replace or weaken that CI requirement.

Independent CLI terminal acceptance

Four logical cases across five actual public-chat lifecycles passed on the published unpatched Core/SDK stack, without the local provider patch:

  • Repeated errors produced no acknowledgment prompts or secret canaries. First Ctrl-C reached confirmation; decline returned to input, and subsequent confirmation exited. Ordinary EOF exited normally.
  • Steering graceful/immediate controls and steering EOF exercised the real reader behavior. EOF stopped the reader without cancelling the agent; explicit fixture release completed the turn.
  • Three original real SDK-constructor failures still produced three secondary SDK cleanup errors. The CLI reported those safely and non-modally; it did not fix the SDK.
  • Completed native history contained four messages and matched exact message data, including metadata, after fresh public resume. Cleanup ran once; no CLI prompt/reporter tasks remained before harness safety cleanup.

Published Core 2.0.2 left one orchestrator task pending after immediate cancellation, recorded before harness safety cleanup. This separate Core ownership defect remains; CLI does not promise native inner settlement. The earlier seven-scenario jointly patched stack is historical supporting evidence only, not proof of independent CLI cancellation settlement. Its pre-arm driver timeout and the later pre-session metadata-fixture setup failure are retained as harness failures, not product passes.

Scope limits: synthetic offline fixtures and native-local legacy session IDs; no live account, shared-root UUID, deployed-host, or full Click interactive-startup qualification. Actual installed command help and the full doctor launchers were checked separately.

How to verify

After preparing the declared dependencies:

uv sync --all-extras --dev
uv run pytest -q
uv run pytest -m integration -q
uv run pytest -q tests/test_loop_errors.py tests/test_loop_errors_bounds.py tests/test_session_handoff.py tests/test_steering.py tests/test_interactive_chat_tty_teardown.py tests/test_doctor.py tests/test_doctor_entrypoint.py tests/test_cli_doctor_agent.py

Follow the installed-wheel doctor procedure in .github/workflows/ci.yml. Keep actual failure counts and compare baseline before classifying environment-dependent failures.

Compatibility, cross-host impact, and breaking changes

No public API, command, dependency floor, version bump, host companion PR, or deployment change. The intentional visible change is non-modal privacy-bounded background diagnostics and correct parent-side exit confirmation. Stronger native cancellation settlement remains dependent on a separately qualified future Core release, not this CLI merge.

Unified does not import this interactive CLI path. Agent and Resolve session APIs do not import this reporter. Resolve worker images that later adopt CLI changes need their own qualification; running image closures and all downstream pins remain unchanged. No host update or restart is performed by this PR.

Keep CLI-owned, bounded loop diagnostics active through initialization, idle
and steering input, and cleanup without prompt-toolkit modal error prompts.
Carry prompt-child interrupts to the parent task and preserve release-wins
handoff behavior after child settlement. Add privacy and lifecycle regressions.

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@bkrabach
Brian Krabach (bkrabach) marked this pull request as ready for review October 27, 2026 12:41
@bkrabach
Brian Krabach (bkrabach) merged commit ab2df25 into main Oct 27, 2026
9 checks passed
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.

2 participants