Skip to content

fix: HTTP non-2xx with envelope-shaped JSON body (status:0) is silently treated as success #6

Description

@ConradLu2740

What Happened

evercli treats any HTTP response whose body parses as JSON with status:0 (zero-value / missing field) as success, regardless of the actual HTTP status code. A 500 (or any non-2xx) whose body is envelope-shaped but lacks an explicit status is swallowed — callers see err == nil, checkpoints advance, and users are told the operation succeeded when the backend never recorded anything.

Root cause: json.Unmarshal fills missing fields with the zero value, so any valid JSON body that is envelope-shaped but does not carry an explicit non-zero status is indistinguishable from a real success. The env.Status == 0 branch never inspects resp.StatusCode.

Location: cli/internal/client/http_client.go(*httpClient).do, around lines 274–289:

var env backendEnvelope
if err := json.Unmarshal(raw, &env); err != nil {
    // ... correctly classifies non-JSON bodies by HTTP status ...
}

if env.Status == 0 {            // ← no resp.StatusCode check here
    if out != nil && len(env.Result) > 0 && string(env.Result) != "null" {
        if err := json.Unmarshal(env.Result, out); err != nil {
            return output.Internal(fmt.Errorf("decode result: %w", err))
        }
    }
    return nil                  // ← HTTP 500 also lands here
}

return classifyEnvelopeError(env)

Steps To Reproduce

  1. Start a local httptest/mock server that returns HTTP 500 with body:
    {"requestId":"req-500","result":{}}
  2. Point the CLI at it (EVERCLI_API_BASE_URL=http://127.0.0.1:<port>).
  3. Call any client method (e.g. auth login --api-key, plugin install, import run).
  4. Observe: the call returns success (err == nil) even though the server returned 500.

Expected Behavior

Non-2xx responses must never be treated as success. The env.Status == 0 branch should only apply to 2xx responses; otherwise the error should be classified by HTTP status.

Suggested Fix

if resp.StatusCode/100 != 2 {
    return output.Upstream(resp.StatusCode, http.StatusText(resp.StatusCode), "")
}

Plus a regression test covering "HTTP 500 + envelope-shaped JSON with status:0".

Impact

  • Silent data loss: import run uploads → CreateRecord fails with 500 + partial JSON → CLI reports success → checkpoint advances to uploaded → the memory is never stored and a re-run cannot recover it.
  • plugin install may silently skip RegisterAgent failures.
  • Every API call path is affected.

Environment

  • OS: macOS 26.4 (arm64)
  • evercli --version: 0.31.2
  • Package or plugin: @everme/cli / evercli (Go CLI)

Logs

No real emk_*/evt_* values used in this report; all examples are synthetic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions