Skip to content

Distinguish product defects from user configuration errors in adopter-facing output #328

Open
1 of 1 issue completed

Description

@pengfei-threemoonslab

Summary

Agents Shipgate does not reliably distinguish its own defects from the user's configuration errors in adopter-facing output. A first-contact adopter cannot tell "this repository is unusual and needs configuration" from "this tool is broken", and will reasonably assume the former — then spend time fixing something that was never theirs.

Invariant 4 of #327.

Evidence

Two cases, both observed on real external repositories.

#307 (now fixed) — a product defect routed as user recovery. The duplicate tool_sources[].id collision was a bug in our ID derivation. In agent mode it surfaced as internal_error with a next action of init --minimal — a recovery step the user performs, which discards the detection work and hands back a CHANGE_ME template. The message pointed the user at their repository; the fault was ours.

#321 — a product defect typed as an input error. Sharing one FunctionTool across several LlmAgents raises InputParseError. That exception name and exit code mean "the input you supplied could not be parsed", but the input is a valid, canonical ADK pattern and the defect is in our catalog identity model. An adopter reading InputParseError will look for the malformed file.

The pattern in both: an internal failure inherits an error kind and a next action that imply user fault.

Why this matters more during first contact

Adoption is exactly when the user has the least basis for judging which side the fault is on, and the most willingness to conclude the tool does not work on their repository and stop. A misattributed error at this point is not a cosmetic problem — it is an abandonment path. Every open bug in this tracker was found by pointing the tool at an external repository, and several presented this way.

Requested

A taxonomy, applied consistently:

  • Classify every adopter-facing failure as ours (a defect, a limitation, an unsupported-but-valid shape) or theirs (a genuinely malformed or missing input).
  • A defect must never emit a user-remediation next action. The honest action is to report the failure, name it as ours, and — where possible — offer a route that does not require the user to fix anything.
  • Exit codes and error kinds should follow the classification rather than cutting across it. InputParseError should mean the input was bad.
  • Unsupported-but-valid is a third state and should say so: the repository is fine, the shape is real, we do not handle it yet. This overlaps the relevant_but_unsupported routing in feature(trigger): route Ruby/RubyLLM agent PRs as relevant but unsupported #309 — the vocabulary should be settled once.

Acceptance criteria

Non-goals

Suppressing or softening genuine configuration errors. The point is accurate attribution in both directions.

First implementation slice, still reproducible on main — 2026-09-06

This is not only an error-catalog task. On 20968551, the generated-manifest validation failure still reports internal_error and then routes the coding agent to init --write --minimal --json, expecting the user to fill placeholders. The current test pins that fallback while checking invocation fidelity.

Reproduced through the real CliRunner, injecting only _validate_manifest_text raising ValueError("synthetic schema failure") and running init --workspace <fixture> --write --json:

exit_code: 4
error: internal_error
control.next_action.actor: coding_agent
control.next_action.kind: initialize
next_actions[0].command: agents-shipgate init … --write --minimal --json
next_actions[0].expects: shipgate.yaml renders with placeholder fields you fill in manually.
manifest_written: false

Proposed PR order

  1. Repair this generator-failure route first. Use an existing diagnostic/review route to identify an Agents Shipgate defect. Do not prescribe rewriting the manifest or automatically discarding discovery for a minimal template.
  2. Preserve the nonzero exit, all-false setup permissions, structured error/envelope and stream contract. --minimal remains an explicit user choice, not the canonical remedy for a product exception.
  3. Update the existing regression to preserve invocation/envelope assertions while requiring no repository-edit action and no manifest, workflow or kit writes. Add the counterexample: genuinely malformed user YAML still names its file/field for repair.
  4. Then apply the defect / valid-but-unsupported / malformed-input distinction across detect, doctor and scan. Avoid renaming every exception/exit code before the demonstrated wrong route is fixed; any public grammar change follows STABILITY.md.

#520's reader-coverage misses are the other major instance: name a product extraction limitation as such, without asking users to repair valid source or invent reviewed evidence. #538 provides a concrete binding whose known facts must survive the missing-leaf case.

The init fix can proceed with #533, #516 and #536; coordinate only shared-file edits. It does not wait for a broad decomposition of init or a general taxonomy rewrite.

Implemented first slice — #546

Merged after one GitHub review/address round, full local regression and green CI. Init rendering and validation failures now emit the existing internal-error/setup envelope with details.failure=manifest_generation_failed, details.origin=agents_shipgate, and one report-defect route with no edit or fallback command. Four injected cases prove no manifest/workflow/kit writes; malformed user YAML still points to its file for repair. The remaining cross-command taxonomy acceptance criteria stay open. #547 separately records the newly observed loss of host-inventory failure detail.

Activity

  1. added this to the v0.17.0 milestone on Aug 7, 2026
  2. added
    bugSomething isn't working
    P1Picked up inside the current milestone window; keep about 15 open
    area:agent-modeAgent-mode contract: control envelope, next actions, command strings
    on Aug 7, 2026
  3. pengfei-threemoonslab commented on Aug 20, 2026

    @pengfei-threemoonslab
    ContributorAuthor

    A concrete instance from a first-time adoption walk of google/adk-samples#1745 on 0.16.0b7, in the one field shipgate requires a human to hand-write.

    The user filled agent.declared_purpose[0] with a one-line description that happened to contain a colon:

    declared_purpose:
      - Handle Salesforce and SAP quote-to-cash requests from natural language: read opportunities, create quotes, and create sales orders.

    The unquoted : makes YAML parse the item as a mapping, so the list entry is a dict. scan reports:

    Config error: Invalid shipgate.yaml:
    - agent.declared_purpose.0: Input should be a valid string
    next: Edit .../shipgate.yaml
      why: Loader rejected .../shipgate.yaml: Invalid shipgate.yaml:
    - agent.declared_purpose.0: Input should be a valid string. Fix the manifest in place —
      do not re-run init, which would refuse to overwrite an existing file.
    

    This is correctly classified as a user configuration error, and the "fix in place, do not re-run init" instruction is genuinely good. The gap is that the message names the schema violation but not the cause: a user who typed a colon into a prose sentence is told their string is not a string, with no hint that the colon turned the item into a key/value pair. The fix is not discoverable from the message without knowing YAML semantics.

    Worth special-casing because it lands on the highest-traffic hand-edited field in the product, and prose descriptions attract colons. When a scalar field receives a dict with exactly one key, the loader has enough information to say so: "line 13 parsed as a mapping, not a string — an unquoted : splits a YAML scalar. Quote the value."

  4. removed this from the v0.18.0 milestone on Aug 31, 2026
  5. pengfei-threemoonslab commented on Aug 31, 2026

    @pengfei-threemoonslab
    ContributorAuthor

    Removed from v0.18.0 in the milestone re-cut: #440-cluster gap work, not a four-flags commitment.

  6. pengfei-threemoonslab commented on Sep 4, 2026

    @pengfei-threemoonslab
    ContributorAuthor

    Evidence to add: insufficient_evidence is the largest instance of this pattern. #520 establishes that the value is emitted when source warnings cross a threshold — a statement that shipgate's reader could not enumerate the source — but it is rendered as a release decision the adopter reads as "your repository is unknowable." An adapter gap wearing a verdict's clothes, on the same first contact this issue is about. #393 was one repository-family-wide instance; the corpus round found it on 13 of 48 real-history cases from one rater family.

  7. added
    workstream:maintenanceShared readers, diagnostics, documentation, build and release maintenance.
    P2Queued; valuable but not blocking
    and removed
    P1Picked up inside the current milestone window; keep about 15 open
    on Oct 2, 2026
  8. pengfei-threemoonslab commented on Oct 7, 2026

    @pengfei-threemoonslab
    ContributorAuthor

    Carrying #440's unticked row here: production-rendered regressions for the error taxonomy (see #440 for the original list).

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

    P2Queued; valuable but not blockingarea:agent-modeAgent-mode contract: control envelope, next actions, command stringsbugSomething isn't workingworkstream:maintenanceShared readers, diagnostics, documentation, build and release maintenance.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions