Skip to content

fix(cli): avoid cli-table3 wordWrap deadlock with 4+ subgraphs#2620

Open
kamil-gwozdz wants to merge 1 commit intowundergraph:mainfrom
kamil-gwozdz:fix/compose-table-deadlock-2619
Open

fix(cli): avoid cli-table3 wordWrap deadlock with 4+ subgraphs#2620
kamil-gwozdz wants to merge 1 commit intowundergraph:mainfrom
kamil-gwozdz:fix/compose-table-deadlock-2619

Conversation

@kamil-gwozdz
Copy link

@kamil-gwozdz kamil-gwozdz commented Mar 10, 2026

Summary

Fixes #2619

wgc router compose deadlocks when federation composition produces errors and the config references 4+ subgraphs. The process prints the error header but hangs indefinitely before rendering the error table.

Root Cause

cli-table3's built-in wordWrap: true option triggers an expensive code path through string-widthemoji-regex for every word in every table cell. With enough error content (4+ subgraphs), this blocks the Node.js main thread indefinitely, preventing signal handling and event loop progress.

Fix

  • Introduced a lightweight wrapText() utility (cli/src/wrap-text.ts) that wraps text on word boundaries using simple character counting — no heavy regex dependencies.
  • Exported a TABLE_CONTENT_WIDTH constant (116 = 120 column width − 2 padding − 2 borders) to avoid magic numbers.
  • Pre-wrap error/warning messages before inserting them into the table.
  • Removed wordWrap: true from all affected table configurations.
  • Table output looks identical to before; only the wrapping mechanism changes.

Files Changed

File Change
cli/src/wrap-text.ts New standalone utility with wrapText() and TABLE_CONTENT_WIDTH — zero dependencies
cli/src/utils.ts Re-exports wrapText for backward compatibility
cli/src/commands/router/commands/compose.ts Fixed 4 table instances (errors + warnings for main and feature flag compositions)
cli/src/handle-composition-result.ts Fixed 3 table instances (composition errors, deployment errors, warnings)
cli/test/wrap-text.test.ts 10 unit tests for wrapText, including a regression test verifying 500-word inputs complete in <1s
cli/test/compose-error-table.test.ts Integration test reproducing the exact issue — 4 subgraphs with circular @override, verifying the error table renders without hanging (5s timeout)

Testing

  • wrap-text.test.ts — 10 tests covering word wrapping edge cases, empty inputs, long words, paragraph preservation, and a performance regression test.
  • compose-error-table.test.ts — Integration test using the exact reproduction schemas from the issue (4 subgraphs with circular @override between subgraph-b and subgraph-c). Runs federateSubgraphs, asserts composition fails, renders the error table through the fixed code path, and verifies output correctness within a 5-second timeout.
  • All 11 new tests pass. Existing CLI tests unaffected.

Summary by CodeRabbit

  • Bug Fixes

    • CLI compose commands and result handling now consistently wrap long error and warning messages for readable table output and avoid hangs when rendering complex composition errors.
  • Tests

    • Added tests validating the new text-wrapping behavior, edge cases, performance, and a compose error-table test to ensure reliable error reporting.

@github-actions github-actions bot added the cli label Mar 10, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4dcbd8af-f31d-48e9-aa22-a1adfa57a402

📥 Commits

Reviewing files that changed from the base of the PR and between 1c30315 and 5d92b16.

📒 Files selected for processing (6)
  • cli/src/commands/router/commands/compose.ts
  • cli/src/handle-composition-result.ts
  • cli/src/utils.ts
  • cli/src/wrap-text.ts
  • cli/test/compose-error-table.test.ts
  • cli/test/wrap-text.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • cli/src/handle-composition-result.ts
  • cli/src/wrap-text.ts
  • cli/src/utils.ts
  • cli/test/wrap-text.test.ts

Walkthrough

Adds a text-wrapping utility and replaces cli-table3 wordWrap usage by pre-wrapping error and warning messages before table insertion; also adds unit tests for the wrapper and a test exercising the compose error table rendering path.

Changes

Cohort / File(s) Summary
Compose command & result handling
cli/src/commands/router/commands/compose.ts, cli/src/handle-composition-result.ts
Remove table wordWrap usage and pass wrapped message strings (via wrapText(..., TABLE_CONTENT_WIDTH)) into error and warning table rows.
New text-wrap utility
cli/src/wrap-text.ts
Adds TABLE_CONTENT_WIDTH constant and wrapText(text: string, maxWidth: number): string that wraps text by words across paragraphs without relying on cli-table3's wordWrap.
Utility re-export
cli/src/utils.ts
Re-export wrapText from ./wrap-text.js (public re-export added).
Unit tests for wrapper
cli/test/wrap-text.test.ts
New comprehensive tests for wrapText: short inputs, boundary wrapping, newline/paragraph preservation, long single-word behavior, spacing, realistic long messages, and performance.
Compose error table test
cli/test/compose-error-table.test.ts
Adds a test reproducing the multi-subgraph compose error-table rendering path (4 subgraphs with circular overrides) and asserts table output contains expected content and does not hang.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: avoiding cli-table3 wordWrap deadlock when composing with 4+ subgraphs, which directly addresses issue #2619.
Linked Issues check ✅ Passed The PR fully addresses issue #2619 by implementing a lightweight wrapText utility to replace cli-table3's wordWrap, eliminating the deadlock by avoiding expensive emoji-regex processing.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the deadlock: new wrap-text utility, updated table configurations, and comprehensive tests with a regression test for large inputs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
cli/test/wrap-text.test.ts (1)

25-33: Consider tightening the assertion tolerance.

The test asserts line.length <= 35 for a maxWidth of 30, which is a 16% overage. While wrapText can legitimately exceed maxWidth for single words longer than the limit, this test's assertion is loose enough that it wouldn't catch certain wrapping bugs.

For word-boundary wrapping where words are short, lines should be at or below maxWidth. Consider splitting the assertion:

for (const line of lines) {
  // Lines should not exceed maxWidth unless they contain a single word longer than maxWidth
  const words = line.split(' ');
  if (words.length > 1) {
    expect(line.length).toBeLessThanOrEqual(30);
  }
}

This is a minor suggestion—the current test still validates the core behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/wrap-text.test.ts` around lines 25 - 33, The test's length tolerance
is too loose; update the assertion in the wrap-text.test to enforce maxWidth for
normal wrapped lines: iterate over lines (the lines array from result) and for
each line split into words, and if words.length > 1 assert line.length <=
maxWidth (use the same numeric maxWidth variable used when calling wrapText,
e.g., 30); allow longer lengths only when a single word exceeds maxWidth. This
change targets the test block using input, result, lines and wrapText.
cli/src/handle-composition-result.ts (1)

12-12: Apply the same wrapText fix to handle-proposal-result.ts for consistency.

cli/src/handle-proposal-result.ts currently uses wordWrap: true in multiple Table configurations (lines 26, 32, 38, 49, 61). Update these tables to use wrapText from ./utils.js instead, consistent with the fix applied to handle-composition-result.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/handle-composition-result.ts` at line 12, handle-proposal-result.ts
still uses deprecated/incorrect Table options with wordWrap: true in several
Table configurations; replace those usages by importing and using the wrapText
helper from ./utils.js (same as in handle-composition-result.ts). Specifically,
add import { wrapText } from './utils.js' at the top of
handle-proposal-result.ts if missing, then for each Table definition that sets
wordWrap: true (the tables around the blocks with wordWrap at the spots
corresponding to the earlier lines 26, 32, 38, 49, 61) remove wordWrap and pass
wrapText for the cell wrapping (e.g., use wrapText(cellValue, width) or whatever
wrapText signature is used elsewhere) so the table rendering matches
handle-composition-result.ts behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cli/src/handle-composition-result.ts`:
- Line 12: handle-proposal-result.ts still uses deprecated/incorrect Table
options with wordWrap: true in several Table configurations; replace those
usages by importing and using the wrapText helper from ./utils.js (same as in
handle-composition-result.ts). Specifically, add import { wrapText } from
'./utils.js' at the top of handle-proposal-result.ts if missing, then for each
Table definition that sets wordWrap: true (the tables around the blocks with
wordWrap at the spots corresponding to the earlier lines 26, 32, 38, 49, 61)
remove wordWrap and pass wrapText for the cell wrapping (e.g., use
wrapText(cellValue, width) or whatever wrapText signature is used elsewhere) so
the table rendering matches handle-composition-result.ts behavior.

In `@cli/test/wrap-text.test.ts`:
- Around line 25-33: The test's length tolerance is too loose; update the
assertion in the wrap-text.test to enforce maxWidth for normal wrapped lines:
iterate over lines (the lines array from result) and for each line split into
words, and if words.length > 1 assert line.length <= maxWidth (use the same
numeric maxWidth variable used when calling wrapText, e.g., 30); allow longer
lengths only when a single word exceeds maxWidth. This change targets the test
block using input, result, lines and wrapText.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e2743d5-d797-4e83-8bf3-7412675425e2

📥 Commits

Reviewing files that changed from the base of the PR and between 610f4c0 and 1c30315.

📒 Files selected for processing (5)
  • cli/src/commands/router/commands/compose.ts
  • cli/src/handle-composition-result.ts
  • cli/src/utils.ts
  • cli/src/wrap-text.ts
  • cli/test/wrap-text.test.ts

@kamil-gwozdz kamil-gwozdz force-pushed the fix/compose-table-deadlock-2619 branch from 1c30315 to ea579d1 Compare March 10, 2026 12:21
…rgraph#2619)

Replace cli-table3's built-in wordWrap with a lightweight wrapText() utility
that wraps text on word boundaries using simple character counting. This avoids
the expensive string-width/emoji-regex code path in cli-table3 that causes the
Node.js main thread to block indefinitely when rendering large error tables.

Fixes: wundergraph#2619

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kamil-gwozdz kamil-gwozdz force-pushed the fix/compose-table-deadlock-2619 branch from ea579d1 to 5d92b16 Compare March 10, 2026 12:23
@kamil-gwozdz kamil-gwozdz marked this pull request as ready for review March 10, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wgc router compose deadlocks when rendering composition error table with 4+ subgraphs

1 participant