Skip to content

M1/PR16: review blocker set — typed-spine leaks and silent route rewriting (F01 F02 F03 F04 F06 F12) #17

Closed

Description

@Blind-Striker

Source: multi-agent review of PR #16 (range 3e3e058871...52a5e3b1fd), adversarially verified 2026-08-13. This issue is the canonical record; the scratchpad review artifacts are ephemeral. Fix red-test-first; closed by the PR that lands the fixes.

Checklist

  • F01 — Invalid charset escapes the typed exception spine · CONFIRMED · sev 4/5 · shipped-runtime. Pipeline.ReadBodyAsync (src/OpenCode.Sdk/Internal/Pipeline.cs:148-152) catches only HttpRequestException or IOException or ObjectDisposedException; HttpContent.ReadAsStringAsync throws InvalidOperationException (inner ArgumentException) on Content-Type: application/json; charset=not-an-encoding — verified on modern runtimes and net472. Escapes OpenCodeException spine on every operation; NoThrow cannot intercept (body read at :92 precedes the ErrorBehavior decision at :95-98). Fix: add InvalidOperationException to the filtered catch, wrap as OpenCodeTransportException, keep OperationCanceledException untouched. Test: invalid-charset content asserting OpenCodeTransportException with IOE inner.
  • F02 — Unexpected 2xx enters the API-error channel · CONFIRMED · medium · both M1 operations. Written contract: "Unexpected or malformed 2xx responses are protocol failures" (docs/superpowers/plans/2026-08-13-m1-arc-b-callable-client.md:41). Generated adapters (HealthResponseAdapter.cs:37, same in SessionMessageResponseAdapter, emitted by ResponseAdapterEmitter.cs:86-119) route 201/204/299 to the error envelope instead of OpenCodeTransportException. Fix in ResponseAdapterEmitter: declared 200 = success; other 2xx arm throws OpenCodeTransportException; non-2xx stays on the API-error channel. Regenerate; test both behaviors under default and NoThrow.
  • F03 — Empty union marker leaks ArgumentException, loses raw body · CONFIRMED · high · all seven unions, both adapter paths. {"_tag":""} misses the known-tag map, hits ArgumentException.ThrowIfNullOrEmpty(tag) in UnknownOpenCodeError (Models/UnknownOpenCodeError.cs:19); ReadTolerantError catches only JsonException (ResponseAdapter.cs:93-96) → BCL exception escapes even under NoThrow, RawBody dropped. Same for a 200 payload with "type":"". Contradicts ADR-0009 tolerance. Fix: generated converters classify empty/whitespace markers as malformed → JsonException; adapter catches then yield null error + raw body (non-2xx) or OpenCodeTransportException (malformed success). Keep public ctor guards. Tests across representative unions, both paths.
  • F03-fold / R4 — centralize unknown-carrier construction: ResponseAdapter.Downgrade (ResponseAdapter.cs:99-103) and generated OpenCodeErrorJsonConverter (:53) build the carrier independently — fold a shared runtime factory into the F03 fix to kill carrier-shape drift (UNVERIFIED-FINDER R4; do it because the F03 fix touches both sites anyway).
  • F04 — Dot-segment ids silently rewrite the request path · CONFIRMED · medium · every generated path parameter. Uri.EscapeDataString leaves ./.. unescaped (OpenCodeRoutes.cs:35-39); new Uri(base + route) (Pipeline.cs:87) removes dot segments. Verified: session id .. → /api/session/../message/m1 collapses to /api/message/m1; message id .. → /api/session/s/. Fix: generator-owned route-segment guard rejects exact . and .. (decide whitespace-only too) before URI construction. Tests inspect actual outgoing RequestUri for both parameter positions. Do not fight System.Uri canonicalization without cross-TFM proof.
  • F06 — Undefined ErrorBehavior silently acquires NoThrow semantics · CONFIRMED · low · public options. Pipeline.cs:96-98 tests is ErrorBehavior.Default; (ErrorBehavior)2 suppresses the throw. Fix: exhaustive branch — Default throws on error, NoThrow returns, anything else ArgumentOutOfRangeException. Undefined-value test.
  • F12 — Send-path ObjectDisposedException can escape under a disposal race · PLAUSIBLE · low. SendCoreAsync (Pipeline.cs:125-137) catches only HttpRequestException while ReadBodyAsync wraps ODE. Fix: add ODE to the send-path filtered catch (keep OperationCanceledException passing through; pre-send disposed guard stays outside the catch). Deterministic handler-level ODE test.

Notes

  • Public API names untouched — no baseline churn expected; if a fix forces a baseline change, stop and return to the maintainer.
  • Full verifier text lived in .scratchpad/ultra-review/VERIFICATION-REPORT.md (ephemeral); the mechanisms above are the preserved record.

Activity

  1. Blind-Striker commented on Aug 13, 2026

    @Blind-Striker
    CollaboratorAuthor

    All six findings fixed red-test-first and pushed to PR #16:

    • 082b58c — F01 (invalid charset → OpenCodeTransportException), F12 (send-path ObjectDisposedException wrapped), F06 (undefined ErrorBehavior refused with ArgumentOutOfRangeException before sending).
    • 1de4d78 — F02 (undeclared 2xx → >= 200 and < 300 transport-failure arm via a new UndeclaredSuccessFailure base helper), F03 (converters classify empty/whitespace markers as malformed JsonException; raw body retained on the error path, OpenCodeTransportException on success), F04 (route builders refuse ./.. and whitespace values; outgoing RequestUri escaping pinned by tests). Emitter snapshots updated; 571 tests green; committed generated output regen-verified.

    R4 (fold) — resolved by argument, not code: a shared unknown-carrier factory would require the generic union emitter to special-case one union's construction path, which ADR-0008 forbids (emitters never branch on names). The shared contract both sites use is the public UnknownOpenCodeError constructor itself; the F03 change moves malformed-marker classification in front of both construction sites, which was the drift risk that mattered.

    Note for the record: ns2.0's string.IsNullOrWhiteSpace lacks the [NotNullWhen(false)] flow annotation, so the emitted guard carries an explicit marker is null || disjunct — that is deliberate, not redundancy.

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

    ready-for-agentSpecified enough that the work is mechanical

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions