Skip to content

fix(k8s): prevent circular-JSON crash in catch-block debug logs#364

Open
remidebette wants to merge 1 commit into
actions:mainfrom
instadeepai:fix/issue-329-circular-json-error-serialization
Open

fix(k8s): prevent circular-JSON crash in catch-block debug logs#364
remidebette wants to merge 1 commit into
actions:mainfrom
instadeepai:fix/issue-329-circular-json-error-serialization

Conversation

@remidebette
Copy link
Copy Markdown

@remidebette remidebette commented May 27, 2026

Summary

Six catch blocks across the k8s hooks built debug strings with JSON.stringify(err) inside a template literal. When err is a @kubernetes/client-node HTTP error (response embeds a TLSSocket↔HTTPParser cycle), JSON.stringify throws synchronously before core.debug runs, propagating out of the catch and shadowing the original failure with TypeError: Converting circular structure to JSON.

Adds a formatError() helper that prefers response.body.message, then Error.stack/message, then a safe JSON / String() fallback — never throws.

Linked issues

Relationship to #341

Complementary — #341 fixes a distinct pattern (throw new Error(\… ${JSON.stringify(error)}`)producing{}becauseError.message` is non-enumerable). The two PRs touch disjoint lines.

Tests

New format-error-test.ts with 12 cases including a circular-ref repro mimicking the TLSSocket↔HTTPParser shape. All unit suites pass (66/66); lint, format-check, tsc + ncc clean.

Six catch blocks in the k8s hooks built debug strings with
`JSON.stringify(err)` inside a template literal:

    } catch (err) {
      core.debug(`execPodStep failed: ${JSON.stringify(err)}`)
      const message = (err as any)?.response?.body?.message || err
      throw new Error(`failed to run script step: ${message}`)
    }

When err is a @kubernetes/client-node HTTP error, its response embeds
a TLSSocket <-> HTTPParser cycle. JSON.stringify throws synchronously
before core.debug runs, propagates out of the catch block, and shadows
the original failure with:

    TypeError: Converting circular structure to JSON
        --> starting at object with constructor 'TLSSocket'

Add a defensive formatError() helper that prefers
response.body.message (the diagnostic K8s API surface), then
Error.stack/message, then a String() / safe-JSON fallback for
plain objects, never throwing. Use it at the six unsafe sites.

Closes actions#329.

Complements actions#341, which fixes a related but distinct pattern
(`throw new Error(... JSON.stringify(error) ...)` producing `{}`
because Error.message is non-enumerable). The two PRs touch
disjoint lines.
@remidebette remidebette requested a review from nikola-jokic as a code owner May 27, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: TypeError: Converting circular structure to JSON

1 participant