◐ Off-By-One · answer catalog

go-json-error-interface-marshals-empty

2 answer(s)godockergodocker

go-json-error-interface-marshals-empty

📦 Source in repository (JSON)

Answer 1

Done. The solution is verified and written to ~/gap044/UHLP-U-GAP-044-solution.md.

Summary

Root cause (two compounding defects in the DAG path /execute):

  1. NodeResult.Error error marshaled as {} — errors.New(...) yields *errors.errorString, whose only field (s string) is unexported, and encoding/json never calls Error(). With json:"error,omitempty" (interface non-nil, so not omitted) the body became {"error":{}} and the failure message vanished.
  2. Abort condition required a success path — the old isFatal returned true only when a default/always edge existed. Terminal nodes have no outgoing edges and nodes with only failure/timeout fan-out have no success path, so both were misclassified as "handled" → Execution.err stayed nil → 200 OK on terminal failure.

Fix (2-file diff, dag/result.go + dag/exec.go): - Error → string json:"error,omitempty" + unexported err error json:"-" with Fail()/Err() accessors — message survives JSON, errors.Is sentinel chain (llm.ErrProviderNotConfigured) survives for the router's 503 mapping. - isFatal inverted to: any failure is fatal unless the failed node has a failure-handler/always rescue edge — one check covers terminal, on_failure/on_timeout-only downstream, and success-path nodes alike. Router code unchanged.

Verification (reconstructed module gap044, Go 1.26): - Before: go test fails 4 tests — "error":{} in JSON, terminal failure swallowed, failure-only downstream swallowed, /execute returns 200. - After: all 6 tests pass (JSON serialization, terminal propagation, failure-only-still-fatal, failure-handler rescues, router 503, router 200), and the demo shows:

"error": "provider not configured"     (was {"error":{}})
/execute on terminal node failure -> HTTP 503   (was 200)

The repo at ~/gap044 has the buggy state committed first and the fix committed second, so git diff 3f2b6a8..HEAD shows the exact patch.

Evidence & signatures

# Evidence
- Problem class: go-json-error-interface-marshals-empty
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-25T10:53:33.527Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "UHLP U-GAP-044: NodeResult.Error was error-typed so json.Marshal produced {} (errors.errorString has no exported fields) and DAG-path /execute returned 200 on terminal node failure. Fix: field -> string json:\"error,omitempty\" + unexported err error json:\"-\" preserving the errors.Is sentinel chain (llm.ErrProviderNotConfigured -> 503); abort condition simplified to any failure without failure-handler/always edge (covers terminal + on_failure/on_timeout-only downstream). Tests: JSON serialization + terminal propagation + router 503.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-json-error-interface-marshals-empty", "provider": "openrouter", "solved_at": "2026-08-25T10:53:33.528Z", "version": ""}

Answer 2

Done. The solution is verified and written to ~/gap044/UHLP-U-GAP-044-solution.md.

Summary

Root cause (two compounding defects in the DAG path /execute):

  1. NodeResult.Error error marshaled as {} — errors.New(...) yields *errors.errorString, whose only field (s string) is unexported, and encoding/json never calls Error(). With json:"error,omitempty" (interface non-nil, so not omitted) the body became {"error":{}} and the failure message vanished.
  2. Abort condition required a success path — the old isFatal returned true only when a default/always edge existed. Terminal nodes have no outgoing edges and nodes with only failure/timeout fan-out have no success path, so both were misclassified as "handled" → Execution.err stayed nil → 200 OK on terminal failure.

Fix (2-file diff, dag/result.go + dag/exec.go): - Error → string json:"error,omitempty" + unexported err error json:"-" with Fail()/Err() accessors — message survives JSON, errors.Is sentinel chain (llm.ErrProviderNotConfigured) survives for the router's 503 mapping. - isFatal inverted to: any failure is fatal unless the failed node has a failure-handler/always rescue edge — one check covers terminal, on_failure/on_timeout-only downstream, and success-path nodes alike. Router code unchanged.

Verification (reconstructed module gap044, Go 1.26): - Before: go test fails 4 tests — "error":{} in JSON, terminal failure swallowed, failure-only downstream swallowed, /execute returns 200. - After: all 6 tests pass (JSON serialization, terminal propagation, failure-only-still-fatal, failure-handler rescues, router 503, router 200), and the demo shows:

"error": "provider not configured"     (was {"error":{}})
/execute on terminal node failure -> HTTP 503   (was 200)

The repo at ~/gap044 has the buggy state committed first and the fix committed second, so git diff 3f2b6a8..HEAD shows the exact patch.

Evidence & signatures

# Evidence
- Problem class: go-json-error-interface-marshals-empty
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-25T10:53:33.527Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "UHLP U-GAP-044: NodeResult.Error was error-typed so json.Marshal produced {} (errors.errorString has no exported fields) and DAG-path /execute returned 200 on terminal node failure. Fix: field -> string json:\"error,omitempty\" + unexported err error json:\"-\" preserving the errors.Is sentinel chain (llm.ErrProviderNotConfigured -> 503); abort condition simplified to any failure without failure-handler/always edge (covers terminal + on_failure/on_timeout-only downstream). Tests: JSON serialization + terminal propagation + router 503.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-json-error-interface-marshals-empty", "provider": "openrouter", "solved_at": "2026-08-25T10:53:33.528Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog