Skip to content

[Bug] MCP error classification: network failures marked non-retryable and unactionable operationId retry advice #400

Description

@zxch3n

Affected area

CLI / daemon

Installation method

Built from source

Lody version or commit

lody-oss @ 48249c9, CLI 0.89.3

Operating system

macOS (arm64)

Agent or runtime

N/A — MCP tool error surface

What happened?

During a network outage the MCP tools returned errors whose classification and recovery advice were wrong, which sent a caller down recovery paths that cannot work.

Four distinct defects, all observed in one session:

1. Network failures are marked non-retryable.

{ "code": "INTERNAL_ERROR", "message": "fetch failed", "retryable": false }

A failed fetch is the canonical retryable error. A caller honouring retryable gives up on something that succeeds moments later.

2. COMMAND_REJECTED is used for an unavailable dependency.

{ "code": "COMMAND_REJECTED",
  "message": "Flock document sync failed for <ws>:mf:<machine> (session.local-projects:<machine>): Streams sync failed: network_error" }

"Rejected" states that the command was evaluated and refused on its merits. Here it timed out waiting on a sync. A caller cannot tell "your request was invalid" from "a dependency was unreachable", and the two demand opposite responses.

3. The retry advice is unactionable for never-accepted operations.

{ "code": "SYNC_UNAVAILABLE",
  "message": "Workspace synchronization is temporarily unavailable. Retry the request and reuse the same operationId when present.",
  "retryable": true }

Following that advice exactly — resend with the same operationId and resume: true — returns:

{ "code": "OPERATION_NOT_FOUND", "message": "Operation not found: <id>", "retryable": false }

because the Operation was never persisted. The message conflates two different recovery modes: an accepted Operation is recovered with resume: true, while a never-accepted one must be resent in full. As written it names the mode that cannot work in the case that produced it.

4. The same advice is returned by tools that have no operationId.

lody_session_list — whose input schema contains no operationId — returns the identical SYNC_UNAVAILABLE text telling the caller to reuse one. The generic message leaks into contexts where it is meaningless.

What did you expect?

  • Network/transport failures classified retryable: true.
  • A distinct code for "a dependency was unreachable" versus "the command was refused".
  • Retry guidance that distinguishes not accepted → resend from accepted → resume, ideally by stating which one applies.
  • No operationId guidance on tools that do not take one.

How can we reproduce it?

  1. Degrade outbound network so Streams sync fails.
  2. Call lody_session_create with a caller-chosen operationId; observe INTERNAL_ERROR: fetch failed with retryable: false, and/or COMMAND_REJECTED naming a Flock sync failure.
  3. Call lody_session_create({ operationId, resume: true }) as the SYNC_UNAVAILABLE message instructs; observe OPERATION_NOT_FOUND.
  4. Call lody_session_list (no operationId in its schema); observe the same SYNC_UNAVAILABLE text advising operationId reuse.

How often does it happen?

Every time

Relevant log output

(MCP tool responses, reproduced verbatim above; no additional daemon log lines are required to observe this)

Additional context

This is cheap to fix relative to its cost: agents and scripts branch on code and retryable, so a misclassification turns a transient outage into a hard failure or an infinite wrong-mode retry.

Suggested fix:

  1. Map transport/fetch/timeout failures to a retryable classification consistently.
  2. Reserve COMMAND_REJECTED for validation/authorization refusals; add or reuse a dependency-unavailable code for sync failures.
  3. Make the SYNC_UNAVAILABLE message state the acceptance status, or split it into two messages, so the caller knows whether to resend or resume.
  4. Attach operationId guidance only to tools whose schema accepts one.

Related: the underlying outage was widened by a Streams reconnect loop, and it also made session creation fail outright; both are filed separately.

Before submitting

  • I searched the existing issues and did not find a duplicate.
  • This report concerns an open-source component in this repository, not a hosted service, Web or mobile app, account, or billing issue.
  • This is not a security vulnerability; security reports follow the repository's security policy.
  • I removed credentials, private source, conversations, prompts, personal data, and other sensitive information.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions