Skip to content

fix: stop solveUntilPhase when solver reaches terminal state (#267) - #272

Open
iprasen wants to merge 1 commit into
tscircuit:mainfrom
iprasen:fix/solve-until-phase-terminal-267
Open

iprasen wants to merge 1 commit into
tscircuit:mainfrom
iprasen:fix/solve-until-phase-terminal-267

Conversation

@iprasen

@iprasen iprasen commented Sep 18, 2026

Copy link
Copy Markdown

Summary

Fixes an infinite loop in LayoutPipelineSolver.solveUntilPhase(phase: string) when the solver reaches a terminal state (solved or failed) before reaching the target phase.

Root Cause

In lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts:

solveUntilPhase(phase: string) {
  while (this.getCurrentPhase() !== phase) {
    this.step()
  }
}

BaseSolver.step() immediately returns when this.solved || this.failed. If phase is unknown, already passed, or if an intermediate phase fails or completes the pipeline without matching phase, step() becomes a no-op that never advances currentPipelineStepIndex. The loop condition this.getCurrentPhase() !== phase remains true indefinitely, blocking the runtime thread.

Solution

Mirror the terminal-state guards from BaseSolver.solve():

solveUntilPhase(phase: string) {
  while (!this.solved && !this.failed && this.getCurrentPhase() !== phase) {
    this.step()
  }
}

Verification

  • Added comprehensive regression test suite in tests/LayoutPipelineSolver/solveUntilPhase-terminal.test.ts with 11 targeted test cases:
    1. Already at target phase: exits immediately without extra steps
    2. Real fixture: cleanly reaches intermediate phase
    3. Pre-failed solver: preserves failure without calling step
    4. Pre-solved solver: does not step for unmet target
    5. Failed active subsolver: halts and preserves subsolver error
    6. Iteration budget exhaustion: terminates promptly instead of spinning
    7. Unknown target phase: safely terminates when solver completes
    8. Requesting an already-passed phase: returns safely
    9. none sentinel: preserves stopping before final step
    10. Thrown child exceptions: correctly propagates errors
    11. Staged solve equivalence: staged execution matches direct solve() output
  • Full repository test suite passes (11/11 new tests passing).
  • bun x --no-install tsc --noEmit: 0 errors.
  • bun run format:check: clean.
  • bun run build: clean ESM + DTS output.

Closes #267

@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
matchpack Ready Ready Preview Sep 18, 2026 5:59pm UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview 05665220 Deployed Sep 18, 2026 by vercel[bot]
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.

solveUntilPhase can loop indefinitely after failure or completion

1 participant