Skip to content

Fix successful exit on provider error finish reasons #108

Description

@byapparov

Context

An observed headless execution using CLI 0.4.3 and Gemini 2.5 Flash completed one file-read tool call, then emitted a terminal message with finish: "error". No final text was recorded; there was no session-error event, and the process exited 0. A downstream consumer classified the result as incomplete despite the successful process exit.

The pinned Google SDK 2.0.54 maps only MALFORMED_FUNCTION_CALL to normalized error. This is strong evidence for a malformed generated function call, but the raw response and exact invalid call were not retained. Do not describe the exact offending call or its cause as proven.

Problem / Goal

Provider finish reasons are values, not necessarily exceptions. processor.ts stores an error finish reason without setting a message error; prompt.ts exits for any finish outside tool-calls/unknown; headless error propagation therefore never runs. A completed zero-finding or zero-text turn must remain distinguishable from an unsuccessful model turn.

Proposed Approach

Handle normalized provider error finishes through the existing session/invocation failure lifecycle. Define a shared classification of terminal reasons so event status and process status cannot disagree. Start with explicit error; document existing limit/filter/unknown semantics instead of indiscriminately failing every non-stop finish. Preserve partial usage and tool activity. This fix must ship independently of richer provider diagnostics or recovery.

Acceptance Criteria

  • A deterministic provider stream fixture emits reasoning followed by an error finish without throwing; the real headless subprocess exits non-zero.
  • For that fixture, message_complete, structured session failure, session_complete, and invocation_complete agree on failure, have stable correlation IDs, and are emitted without duplicate terminal events.
  • Output is flushed before exit; existing cancellation and idle-timeout classifications remain distinct.
  • Successful stop with no text or findings is not failed solely for being empty; a successful final tool call is also covered.
  • A documented terminal-reason matrix covers error, stop, tool-calls, length, content-filter, other/unknown, thrown provider errors, and cancellation. Tests assert intended existing or explicitly changed behavior.
  • Preserve known usage and distinguish unknown usage; failed attempts remain observable.
  • Child-session errors are attributed to the child; they do not automatically fail a parent that handles them successfully.
  • Update the versioned event documentation and release notes; add the successful-stream/error-finish case to release regression coverage.

Reproduction

Supply a test provider that returns an error finish as a normal streamed response with no thrown exception, then run aictrl run --format json. Reproduce the v0.4.3 contradiction before asserting the corrected outcome. A live Gemini call is not required for the regression.

Out of Scope

Automatic retries, raw provider response capture, product review-completion semantics, GitHub checks, dashboards, and unrelated error-handling refactors.

Roadmap Alignment

  • Pillar: EXEC; Q3 2026 pilot-ready executor reliability and code-review quality.
  • Priority: P0 — confirmed production false-success execution.
  • Milestone: Enterprise Observability (CLI repository milestone; repository-specific milestone numbering).
  • Uses existing repository labels; priority and pillar are recorded here because matching labels do not exist in this repository.

References

Sequencing

This is the first implementation step; richer diagnostics and recovery are not prerequisites.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions