Skip to content

fix: stop flows config output from overflowing the call stack - #36

Draft
mlnchk wants to merge 1 commit into
mainfrom
fix-flows-config-output-overflow
Draft

mlnchk wants to merge 1 commit into
mainfrom
fix-flows-config-output-overflow

Conversation

@mlnchk

@mlnchk mlnchk commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

A ~25 MiB flow builder config saved fine through adapty flows config update --config-file ..., but the CLI then crashed with RangeError: Maximum call stack size exceeded (exit 1). The next adapty flows config get on that flow crashed the same way and printed nothing, so the flow was unreadable from the CLI. The write itself succeeded, so the crash reported a failure on a successful save.

Root cause

The human pretty-printer in src/lib/output.ts (renderObject / renderArrayItems) overflows the stack in two ways:

  • Width: lines.push(...renderArrayItems(...)) spreads every rendered line as a function argument. V8 throws once a subtree renders to roughly 100k lines (a ~3 MiB config).
  • Depth: recursion follows the JSON nesting depth and overflows around 5000 levels.

JSON.parse on the response and JSON.stringify on the request body handle the payload fine. Only the display layer crashes. printResponse also ran under --json, because the flows config call sites were not guarded by this.jsonEnabled(), so there was no escape hatch.

Changes

  1. Guard printResponse under --json in flows config get, update and validate, mirroring flows config preview. --json now serializes through oclif only and never enters the recursive renderer.
  2. Summarize the config blob in the human output of flows config get / update. It prints the scalar fields (updated_at, status, publication_status, transform_error, publication_error) plus one line for config with screen count, locale count and byte size. --json still returns the full payload unchanged.
  3. Make output.ts overflow-proof as a backstop for every other command: append lines with a loop instead of push(...spread), cap recursion at 50 levels and print the rest of that subtree as a truncated single JSON line (or a marker when JSON.stringify itself cannot serialize it).

Tests

  • test/lib/output.test.ts: renders a >5 MiB, 150k-item array and a 6000-level nested object and array through printResponse without throwing.
  • test/commands/flows.test.ts: config get prints the summary and hides the blob in human mode, returns the exact DTO under --json, and survives a ~5 MiB config with 1000 nesting levels in both modes.

pnpm build, pnpm lint and pnpm test (371 passing) are green.

Not in scope: --expected-updated-at stays a millisecond integer; the wire format and --json shape are unchanged.

🤖 Generated with Claude Code

A ~25 MiB builder config saved fine through PUT but crashed the CLI with
RangeError: Maximum call stack size exceeded, and the following
`flows config get` crashed the same way, so the flow was unreadable from
the CLI. The crash is in the human renderer (src/lib/output.ts), which
spread whole rendered subtrees into push() (argument-count overflow at
~100k array items) and recursed as deep as the JSON nests.

- flows config get/update/validate: skip the human renderer under --json,
  as preview already does; --json now never touches the recursive path.
- flows config get/update: print scalar fields plus a one-line config
  summary (screens, locales, bytes) instead of the raw blob; the full
  config stays in --json.
- output.ts: append lines with a loop instead of push(...spread), cap
  recursion at 50 levels and fall back to a truncated JSON line (or a
  marker when JSON.stringify itself cannot serialize the subtree).

This branch has not been deployed

No deployments
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.

1 participant