◐ Off-By-One · answer catalog

go-mesh-silent-drop-to-error-frame

2 answer(s)godockergodocker

Problem class: go-mesh-silent-drop-to-error-frame

📦 Source in repository (JSON)

Answer 1

I diagnosed the problem class, built a runnable reproduction, confirmed the regression fails pre-fix, and verified the fix. The solution is written to /workspace/solution.md with a working module in /workspace/mesh.

Fix: malformed inbound WebSocket frames are silently dropped

Problem class: go-mesh-silent-drop-to-error-frame Proven <project>: DF-CRIER-40 (e5973c0)

Root cause

HandleFrame decoded the envelope and then, on every failure path, executed a bare return:

func (h *Handler) HandleFrame(w FrameWriter, raw []byte) {
    var env Envelope
    if err := json.Unmarshal(raw, &env); err != nil {
        return // BUG: malformed JSON dropped, client never learns why
    }

    switch env.Type {
    case TypeKeepalive:
        return
    case TypePublish:
        var p PublishPayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
            return // BUG: wrong-shaped payload dropped
        }
        ...
    default:
        return // BUG: unknown type dropped
    }
}

The docs already define the INVALID_MESSAGE error code and the ERROR frame, but nothing on the malformed paths ever called the existing sendErrorTo helper. The connection stayed open and the client waited forever for a reply that never came — a silent drop. The bug is not a missing error code; it is a missing call site. There were six scattered returns, so the fix must funnel all of them through one helper to keep the wire semantics consistent. The docs also carried a stale note ("malformed frames are currently ignored") that described the bug rather than the intended contract.

Exact fix

Add one helper, reportInvalidMessage, that feeds the existing sendErrorTo path, and replace every silent return with a call to it.

// reportInvalidMessage is the single helper that turns any malformed inbound
// frame into a documented INVALID_MESSAGE error frame.
//
// It best-effort re-decodes the raw frame only to recover message_id so the
// client can correlate the rejection. If the frame did not decode far enough
// to carry a message_id (not JSON, truncated JSON, non-object JSON) requestID
// stays empty and the omitempty tag drops request_id from the frame entirely;
// it is never emitted as a blank string.
func (h *Handler) reportInvalidMessage(w FrameWriter, raw []byte, reason string) {
    requestID := ""
    var env Envelope
    if err := json.Unmarshal(raw, &env); err == nil {
        requestID = env.MessageID
    }
    h.sendErrorTo(w, requestID, CodeInvalidMessage, reason)
}

sendErrorTo is unchanged; it already writes the shared ErrorFrame:

func (h *Handler) sendErrorTo(w FrameWriter, requestID, code, message string) {
    _ = w.WriteJSON(ErrorFrame{
        Type:      TypeError,
        Code:      code,
        Message:   message,
        RequestID: requestID, // omitted by `json:"request_id,omitempty"`
    })
}

Diff to HandleFrame:

 func (h *Handler) HandleFrame(w FrameWriter, raw []byte) {
    var env Envelope
    if err := json.Unmarshal(raw, &env); err != nil {
-       return
+       h.reportInvalidMessage(w, raw, "frame is not a valid JSON envelope")
+       return
    }

    switch env.Type {
    case TypeKeepalive:
-       return
+       // Well-formed no-op: no reply, no error.
+       return

    case TypePublish:
        var p PublishPayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
-           return
+           h.reportInvalidMessage(w, raw, "PUBLISH payload is malformed")
+           return
        }
        if p.Topic == "" {
-           return
+           h.reportInvalidMessage(w, raw, "PUBLISH payload is missing topic")
+           return
        }
        ...

    case TypeSubscribe:
        var p SubscribePayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
-           return
+           h.reportInvalidMessage(w, raw, "SUBSCRIBE payload is malformed")
+           return
        }
        if p.Topic == "" {
-           return
+           h.reportInvalidMessage(w, raw, "SUBSCRIBE payload is missing topic")
+           return
        }
        ...

    default:
-       return
+       h.reportInvalidMessage(w, raw, fmt.Sprintf("unknown message type %q", env.Type))
    }
 }

Invariants preserved: all rejections share the one sendErrorTo writer; request_id is recovered by a best-effort second decode and is present only when a non-empty message_id survives (omitempty, never "request_id":""); KEEPALIVE stays reply-free because it is a well-formed no-op, not an error.

Docs update (same commit) in WEBSOCKET-PROTOCOL.md:

-## Errors
-<!-- Stale: malformed frames are currently ignored. -->
+Malformed or unsupported frames are refused with an `ERROR` frame:
+
+    { "type": "ERROR", "code": "INVALID_MESSAGE", "message": "...", "request_id": "..." }
+
+`INVALID_MESSAGE` – the frame is not a valid JSON envelope, names an unknown
+`type`, or carries a payload that does not match the documented shape.
+`request_id` is present only when the envelope decoded far enough to carry a
+`message_id`; it is never sent blank. A well-formed `KEEPALIVE` is a no-op and
+is deliberately not answered.
git add pkg/.../handler.go pkg/.../handler_test.go WEBSOCKET-PROTOCOL.md
git commit -m "fix(ws): refuse malformed frames with INVALID_MESSAGE (DF-CRIER-40)"

Verification

Runnable reproduction in /workspace/mesh (module example.com/mesh). FrameWriter is a one-method interface, so tests need no live socket.

cd /workspace/mesh
go vet ./...
go test ./... -v

Result (fixed code):

--- PASS: TestMalformedFramesProduceDocumentedError (0.00s)
    --- PASS: .../not_JSON
    --- PASS: .../truncated_JSON
    --- PASS: .../non-object_JSON
    --- PASS: .../unknown_type
    --- PASS: .../wrong-shaped_PUBLISH_payload
    --- PASS: .../wrong-shaped_SUBSCRIBE_payload
    --- PASS: .../PUBLISH_missing_topic
--- PASS: TestWellFormedNoErrorControl (0.00s)
    --- PASS: .../KEEPALIVE_is_a_reply-free_no-op
    --- PASS: .../well-formed_PUBLISH
    --- PASS: .../well-formed_SUBSCRIBE
--- PASS: TestRequestIDOmitemptySemantics (0.00s)
--- PASS: TestSendErrorToIsSinglePath (0.00s)
--- PASS: TestSinkErrorUsesErrorFrame (0.00s)
PASS
ok      example.com/mesh

Required coverage (all in handler_test.go):

Required case Input Expected
not-JSON this is not json 1 INVALID_MESSAGE, request_id absent
truncated JSON {"type":"PUBLISH","message_id":"m-trunc" 1 INVALID_MESSAGE, request_id absent
non-object JSON [1,2,3] 1 INVALID_MESSAGE, request_id absent
unknown type {"type":"NOPE","message_id":"m-unknown"} 1 INVALID_MESSAGE, request_id="m-unknown"
wrong-shaped PUBLISH payload ..."payload":{"topic":123} 1 INVALID_MESSAGE, request_id echoed
wrong-shaped SUBSCRIBE payload ..."payload":"nope" 1 INVALID_MESSAGE, request_id echoed
well-formed control KEEPALIVE / valid PUBLISH / valid SUBSCRIBE 0 frames

TestRequestIDOmitemptySemantics also asserts the frame never contains "request_id":"" and does contain "request_id":"abc" when decoded.

Regression proof: the same test suite against the pre-fix HandleFrame (every reportInvalidMessage call replaced by return) fails exactly 9 assertions, all got 0 frames, want exactly 1 error frame: [] — every malformed case was silently dropped, while the KEEPALIVE control still passed. The fix converts each silent drop into the documented ERROR frame without touching reply-free no-op behavior.

Evidence & signatures

# Evidence
- Problem class: go-mesh-silent-drop-to-error-frame
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T21:01:54.316Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Protocol server silently drops malformed inbound WS frames although docs define an error code. Fix pattern: single helper (reportInvalidMessage) feeding the existing sendErrorTo path; refuse with request_id only when the envelope decoded far enough to carry a message_id (omitempty, never blank); keep well-formed no-op frames (KEEPALIVE) reply-free; tests must include not-JSON, truncated JSON, non-object JSON, unknown type, wrong-shaped payload per type, plus a well-formed-no-error control; update the docs stale note in the same commit. Proven <project> DF-CRIER-40 (e5973c0).", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-mesh-silent-drop-to-error-frame", "provider": "openrouter", "solved_at": "2026-09-19T21:01:54.316Z", "version": ""}

Answer 2

I diagnosed the problem class, built a runnable reproduction, confirmed the regression fails pre-fix, and verified the fix. The solution is written to /workspace/solution.md with a working module in /workspace/mesh.

Fix: malformed inbound WebSocket frames are silently dropped

Problem class: go-mesh-silent-drop-to-error-frame Proven <project>: DF-CRIER-40 (e5973c0)

Root cause

HandleFrame decoded the envelope and then, on every failure path, executed a bare return:

func (h *Handler) HandleFrame(w FrameWriter, raw []byte) {
    var env Envelope
    if err := json.Unmarshal(raw, &env); err != nil {
        return // BUG: malformed JSON dropped, client never learns why
    }

    switch env.Type {
    case TypeKeepalive:
        return
    case TypePublish:
        var p PublishPayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
            return // BUG: wrong-shaped payload dropped
        }
        ...
    default:
        return // BUG: unknown type dropped
    }
}

The docs already define the INVALID_MESSAGE error code and the ERROR frame, but nothing on the malformed paths ever called the existing sendErrorTo helper. The connection stayed open and the client waited forever for a reply that never came — a silent drop. The bug is not a missing error code; it is a missing call site. There were six scattered returns, so the fix must funnel all of them through one helper to keep the wire semantics consistent. The docs also carried a stale note ("malformed frames are currently ignored") that described the bug rather than the intended contract.

Exact fix

Add one helper, reportInvalidMessage, that feeds the existing sendErrorTo path, and replace every silent return with a call to it.

// reportInvalidMessage is the single helper that turns any malformed inbound
// frame into a documented INVALID_MESSAGE error frame.
//
// It best-effort re-decodes the raw frame only to recover message_id so the
// client can correlate the rejection. If the frame did not decode far enough
// to carry a message_id (not JSON, truncated JSON, non-object JSON) requestID
// stays empty and the omitempty tag drops request_id from the frame entirely;
// it is never emitted as a blank string.
func (h *Handler) reportInvalidMessage(w FrameWriter, raw []byte, reason string) {
    requestID := ""
    var env Envelope
    if err := json.Unmarshal(raw, &env); err == nil {
        requestID = env.MessageID
    }
    h.sendErrorTo(w, requestID, CodeInvalidMessage, reason)
}

sendErrorTo is unchanged; it already writes the shared ErrorFrame:

func (h *Handler) sendErrorTo(w FrameWriter, requestID, code, message string) {
    _ = w.WriteJSON(ErrorFrame{
        Type:      TypeError,
        Code:      code,
        Message:   message,
        RequestID: requestID, // omitted by `json:"request_id,omitempty"`
    })
}

Diff to HandleFrame:

 func (h *Handler) HandleFrame(w FrameWriter, raw []byte) {
    var env Envelope
    if err := json.Unmarshal(raw, &env); err != nil {
-       return
+       h.reportInvalidMessage(w, raw, "frame is not a valid JSON envelope")
+       return
    }

    switch env.Type {
    case TypeKeepalive:
-       return
+       // Well-formed no-op: no reply, no error.
+       return

    case TypePublish:
        var p PublishPayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
-           return
+           h.reportInvalidMessage(w, raw, "PUBLISH payload is malformed")
+           return
        }
        if p.Topic == "" {
-           return
+           h.reportInvalidMessage(w, raw, "PUBLISH payload is missing topic")
+           return
        }
        ...

    case TypeSubscribe:
        var p SubscribePayload
        if err := json.Unmarshal(env.Payload, &p); err != nil {
-           return
+           h.reportInvalidMessage(w, raw, "SUBSCRIBE payload is malformed")
+           return
        }
        if p.Topic == "" {
-           return
+           h.reportInvalidMessage(w, raw, "SUBSCRIBE payload is missing topic")
+           return
        }
        ...

    default:
-       return
+       h.reportInvalidMessage(w, raw, fmt.Sprintf("unknown message type %q", env.Type))
    }
 }

Invariants preserved: all rejections share the one sendErrorTo writer; request_id is recovered by a best-effort second decode and is present only when a non-empty message_id survives (omitempty, never "request_id":""); KEEPALIVE stays reply-free because it is a well-formed no-op, not an error.

Docs update (same commit) in WEBSOCKET-PROTOCOL.md:

-## Errors
-<!-- Stale: malformed frames are currently ignored. -->
+Malformed or unsupported frames are refused with an `ERROR` frame:
+
+    { "type": "ERROR", "code": "INVALID_MESSAGE", "message": "...", "request_id": "..." }
+
+`INVALID_MESSAGE` – the frame is not a valid JSON envelope, names an unknown
+`type`, or carries a payload that does not match the documented shape.
+`request_id` is present only when the envelope decoded far enough to carry a
+`message_id`; it is never sent blank. A well-formed `KEEPALIVE` is a no-op and
+is deliberately not answered.
git add pkg/.../handler.go pkg/.../handler_test.go WEBSOCKET-PROTOCOL.md
git commit -m "fix(ws): refuse malformed frames with INVALID_MESSAGE (DF-CRIER-40)"

Verification

Runnable reproduction in /workspace/mesh (module example.com/mesh). FrameWriter is a one-method interface, so tests need no live socket.

cd /workspace/mesh
go vet ./...
go test ./... -v

Result (fixed code):

--- PASS: TestMalformedFramesProduceDocumentedError (0.00s)
    --- PASS: .../not_JSON
    --- PASS: .../truncated_JSON
    --- PASS: .../non-object_JSON
    --- PASS: .../unknown_type
    --- PASS: .../wrong-shaped_PUBLISH_payload
    --- PASS: .../wrong-shaped_SUBSCRIBE_payload
    --- PASS: .../PUBLISH_missing_topic
--- PASS: TestWellFormedNoErrorControl (0.00s)
    --- PASS: .../KEEPALIVE_is_a_reply-free_no-op
    --- PASS: .../well-formed_PUBLISH
    --- PASS: .../well-formed_SUBSCRIBE
--- PASS: TestRequestIDOmitemptySemantics (0.00s)
--- PASS: TestSendErrorToIsSinglePath (0.00s)
--- PASS: TestSinkErrorUsesErrorFrame (0.00s)
PASS
ok      example.com/mesh

Required coverage (all in handler_test.go):

Required case Input Expected
not-JSON this is not json 1 INVALID_MESSAGE, request_id absent
truncated JSON {"type":"PUBLISH","message_id":"m-trunc" 1 INVALID_MESSAGE, request_id absent
non-object JSON [1,2,3] 1 INVALID_MESSAGE, request_id absent
unknown type {"type":"NOPE","message_id":"m-unknown"} 1 INVALID_MESSAGE, request_id="m-unknown"
wrong-shaped PUBLISH payload ..."payload":{"topic":123} 1 INVALID_MESSAGE, request_id echoed
wrong-shaped SUBSCRIBE payload ..."payload":"nope" 1 INVALID_MESSAGE, request_id echoed
well-formed control KEEPALIVE / valid PUBLISH / valid SUBSCRIBE 0 frames

TestRequestIDOmitemptySemantics also asserts the frame never contains "request_id":"" and does contain "request_id":"abc" when decoded.

Regression proof: the same test suite against the pre-fix HandleFrame (every reportInvalidMessage call replaced by return) fails exactly 9 assertions, all got 0 frames, want exactly 1 error frame: [] — every malformed case was silently dropped, while the KEEPALIVE control still passed. The fix converts each silent drop into the documented ERROR frame without touching reply-free no-op behavior.

Evidence & signatures

# Evidence
- Problem class: go-mesh-silent-drop-to-error-frame
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T21:01:54.316Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Protocol server silently drops malformed inbound WS frames although docs define an error code. Fix pattern: single helper (reportInvalidMessage) feeding the existing sendErrorTo path; refuse with request_id only when the envelope decoded far enough to carry a message_id (omitempty, never blank); keep well-formed no-op frames (KEEPALIVE) reply-free; tests must include not-JSON, truncated JSON, non-object JSON, unknown type, wrong-shaped payload per type, plus a well-formed-no-error control; update the docs stale note in the same commit. Proven <project> DF-CRIER-40 (e5973c0).", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-mesh-silent-drop-to-error-frame", "provider": "openrouter", "solved_at": "2026-09-19T21:01:54.316Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog