Skip to content

test(cli): TestRecommendationCompletenessCommand/savingsplans-database/api-error is flaky in CI #2140

Open

Description

@cristim

Summary

The integration subtest TestRecommendationCompletenessCommand/savingsplans-database/api-error (cmd/recommendation_completeness_test.go, added in #2130) failed once in CI on a docs-only PR branch, then passed on a rerun of semantically identical code.

Evidence

A docs-only change cannot affect this test, so the failure indicates nondeterminism in the test or its fixtures (timing, map iteration order, or simulated API-error propagation).

Expected behaviour

The subtest is deterministic: same inputs, same verdict, every run.

Suggested investigation

  • Run the subtest in a loop with -count=50 -shuffle=on locally to reproduce.
  • Check for map-order dependence in the synthetic operation counts logged by the test (synthetic operations=map[...]) and for timing assumptions in the api-error path.

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. cristim commented on Oct 16, 2026

    @cristim
    MemberAuthor

    Claiming: working on this [claude-cli-impl]

  3. cristim commented on Oct 16, 2026

    @cristim
    MemberAuthor

    Investigation by [claude-cli-impl] on origin/main 817ab4b. I'm releasing the claim with no code change, because the failure does not reproduce and a fix without a root cause would be speculative.

    What failed in run 37624743342 (fc23e47, Go 1.26.6, -race, Linux)

    • The assertion was require.Contains(logs, "fixture denied recommendations"), now at cmd/recommendation_completeness_test.go:78.
    • The child process got GetSavingsPlansPurchaseRecommendation ... StatusCode: 400 ... deserialization failed, failed to copy error response body, read tcp 127.0.0.1:...: use of closed network connection.
    • The fixture's 400 was correct and was counted once by the proxy. The client's own socket was closed locally while it read the error body, so the fixture JSON was never parsed.
    • This is the only occurrence in the 10 most recent failed CI runs.

    Reproduction attempts (about 5,000 runs, 0 failures)

    • The single subtest at -count=100, under -race.
    • 8, 24 and 60 parallel processes, up to about 6x CPU oversubscription, with -cpu 1,2.
    • The child process with GOMAXPROCS=1 and 2.
    • The fixture writing its response slowly, 3ms per write.
    • The full TestRecommendationCompletenessCommand: 12 processes × 6 runs, about 2,500 subtests.

    Ruled out

    • Map ordering: the SP path is sequential (collection_sp.go:121-146).
    • Context cancellation: a cancel would surface canceledErr, not the raw net error.
    • Production CloseIdleConnections: there are no callers. The only one is the child test's cleanup, which runs after Execute.
    • The fixture closing the connection: that would show EOF or reset, and the 40s deadline is not reached.

    Remaining hypotheses (unproven)

    1. A rare net/http persistConn close on a loaded 2-core runner. Candidates are closeConnIfStillIdle, the writeLoop close and the readLoop peek-fail close.
    2. A Go 1.26.6 transport issue. CI was on 1.26.6, and go.mod is now 1.26.9.
    3. A race in the child's http.DefaultTransport swap. This is unlikely, because the SDK uses its own client.

    Next step if it recurs: capture the failing run ID and Go version. To instrument it, build with a -overlay copy of persistConn.closeLocked that prints a stack when the connection closes mid-body, and run that on Linux with limited CPU.

    I'm adding status/needs-info until a second occurrence gives more data.

  4. removed their assignment
    on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions