← Back to Blog
DevOps2026-05-05Β·36 min read

Postmortem: How a Bun 1.2 Runtime Bug and Node.js 22 Interop Caused Our API to Return Incorrect JSON for 30 Minutes

By ANKUSH CHOUDHARY JOHAL

Postmortem: How a Bun 1.2 Runtime Bug and Node.js 22 Interop Caused Our API to Return Incorrect JSON for 30 Minutes

Current Situation Analysis

The production Payment API experienced a 30-minute SEV-1 incident where downstream clients received malformed JSON responses, triggering widespread payment processing failures. The core pain point stemmed from an unvalidated assumption that JSON.stringify() behavior and CJS-to-ESM interoperability are deterministic across JavaScript runtimes.

Failure Modes:

  • Missing Required Fields: ~12% of responses lacked critical payment payload fields because the custom toJSON() serialization method was silently skipped.
  • Metadata Leakage: ~8% of responses contained transpilation artifacts (e.g., __esModule, __BUNDLE__) that should have been stripped, causing client-side schema validation failures.

Why Traditional Methods Failed:

  • Runtime-Specific Transpilation Assumptions: The shared DTO library was transpiled with @babel/preset-env targeting Node.js 22, relying on specific property descriptor behavior. Bun 1.2's CJS interop layer altered enumeration rules, breaking the implicit contract.
  • Lack of Cross-Runtime Serialization Testing: CI/CD pipelines only validated JSON output against a single runtime baseline, missing interop edge cases.
  • Immediate Full Rollout: Deploying a major runtime upgrade (Node.js 22 β†’ Bun 1.2) without a canary phase or runtime-matrix testing amplified the blast radius when the regression triggered.

WOW Moment: Key Findings

Approach toJSON() Invocation Rate Serialization Correctness Cold Start Latency CJS Interop Property Enumeration Accuracy
Node.js 22.1.0 (Baseline) 100% 100% 115ms 100%
Bun 1.2.0 (Buggy) 80% 92% 42ms 85%
Bun 1.2.1 (Patched) 100% 100% 43ms 100%

Key Findings:

  • Bun 1.2's CJS interop regression caused toJSON() to be marked non-enumerable for ~20% of DTO instances, depending on import resolution paths.
  • The regression also incorrectly flipped enumeration flags on Babel-injected metadata, causing JSON.stringify() to leak internal properties.
  • Patched Bun 1.2.1 restored enumeration parity with Node.js 22 while preserving the ~63% cold start latency improvement, confirming the performance benefit is viable once interop correctness is guaranteed.

Core Solution

Resolution required immediate rollback, followed by architectural and pipeline hardening to eliminate runtime-specific serialization drift.

1. Cross-Runtime JSON Schema Validation in CI/CD Implemented strict schema validation that runs against both Node.js and Bun runtimes. This catches enumeration and serialization drift before deployment.

// ci/validation/matrix-runner.js
import { execSync } from 'child_process';
import { validatePayload } from './schema-validator.js';

const runtimes = ['node', 'bun'];
const testCases = ['payment-request', 'payment-response', 'refund-dto'];

for (const runtime of runtimes) {
  console.log(`[CI] Running serialization tests on ${runtime}...`);
  for (const testCase of testCases) {
    const output = execSync(`${runtime} ./dist/serialize-test.js ${testCase}`).toString();
    const result = JSON.parse(output);
    if (!validatePayload(result, testCase)) {
      throw new Error(`Schema validation failed on ${runtime} for ${testCase}`);
    }
  }
}

2. Real-Time Validation Sidecar Architecture Deployed an Envoy-based sidecar proxy that intercepts all API responses and validates them against the OpenAPI/JSON schema. Malformed responses trigger immediate alerts and fallback routing.

# k8s/sidecar/envoy-config.yaml
static_resources:
  listeners:
    - name: response_validator
      filter_chains:
        - filters:
            - name: envoy.filters.http.lua
              typed_config:
                inline_code: |
                  function envoy_on_response(response_handle)
                    local body = response_handle:body()
                    local schema = require("payment_schema")
                    if not schema.validate(body) then
                      response_handle:logWarn("MALFORMED_JSON_DETECTED")
                      response_handle:headers():add("X-Validation-Status", "FAIL")
                    end
                  end

3. ESM-Native Migration Strategy Deprecated Node.js 22-specific CJS transpilation for Bun-based services. Migrated shared libraries to native ESM with explicit exports, bypassing CJS interop enumeration quirks entirely.

// package.json (shared-dto-lib)
{
  "type": "module",
  "exports": {
    ".": "./dist/index.js",
    "./dto": "./dist/dto.js"
  }
}

4. Runtime Pinning & Mandatory Canary Periods All deployment manifests now pin exact runtime versions. Minor runtime upgrades require a 24-hour canary deployment with automated JSON schema validation and error-rate monitoring before full rollout.

Pitfall Guide

  1. Implicit toJSON() Reliance Without Schema Enforcement: Assuming JSON.stringify() will always invoke custom serialization methods. Always pair DTO serialization with strict JSON schema validation at runtime and in CI.
  2. CJS-to-ESM Interop Property Descriptor Assumptions: Different runtimes handle Babel-injected property descriptors (enumerable, configurable) differently during module resolution. Never assume enumeration parity across Node.js, Bun, or Deno.
  3. Single-Runtime CI/CD Validation Gaps: Testing serialization only against one runtime masks interop regressions. Implement a runtime matrix in CI to validate identical output across all supported engines.
  4. Skipping Canary Periods for Runtime Upgrades: Swapping JavaScript runtimes is a high-risk infrastructure change. Always deploy to a canary subset with automated response validation and rollback triggers.
  5. Transpilation Metadata Leakage: Babel and other transpilers inject internal markers (__esModule, __BUNDLE__). If enumeration flags are altered by the runtime, these leak into production payloads. Configure transpilers to strip metadata or use native ESM.
  6. Lack of Production-Side Response Validation: Relying solely on client-side error reporting delays incident detection. Deploy validation sidecars or API gateways that enforce schema compliance and alert within seconds of malformed output.

Deliverables

  • Blueprint: Cross-Runtime Serialization Validation & Canary Deployment Architecture (covers Envoy sidecar routing, CI matrix configuration, and ESM migration paths)
  • Checklist: Runtime Upgrade & Interop Safety Checklist (pre-deployment validation steps, canary monitoring thresholds, rollback criteria, and schema enforcement requirements)
  • Configuration Templates: Ready-to-use CI/CD pipeline matrix configs, Kubernetes canary deployment manifests, and Envoy/Lua validation sidecar templates for immediate integration