Skip to content

No request cancellation — race conditions on concurrent pipeline runs #7

@sgardoll

Description

@sgardoll

Summary

All 7 fetch() calls in app.js have no AbortController. The pipelineState.isRunning guard only prevents UI re-entry — it cannot cancel in-flight HTTP requests. This creates race conditions where stale responses overwrite current pipeline state.

Risk Assessment

  • Risk Level: High
  • Likelihood: Medium — occurs when users re-trigger pipeline or use refinement during execution
  • Impact: High — state corruption, wrong code displayed
  • Timeline: As usage grows

Affected Code

fetch() calls without AbortController (lines 1008, 1050, 1108, 1187, 1299, 1362, 1417)

None of the 7 fetch calls use an AbortController signal.

runThinkingPipeline() (line 3723)

if (pipelineState.isRunning) return;

Only guards UI re-entry. Cannot cancel the 3 sequential API calls (Architect → Generator → Dissector) if the user navigates away or wants to restart.

runRefinement() (line 3627)

Does NOT check pipelineState.isRunning, so it can execute concurrently with runThinkingPipeline(), both writing to pipelineState.step2Result.

Race Condition Scenario

  1. User clicks "Run Pipeline" → starts Step 1 (Architect)
  2. User gets impatient after 15s, refreshes and clicks again
  3. Pipeline A's Step 2 response arrives, writes to pipelineState.step2Result
  4. Pipeline B's Step 1 completes, overwrites pipelineState.step1Result
  5. Pipeline B's Step 2 now uses Pipeline B's spec but Step 2 output from Pipeline A is briefly visible

Suggested Fix

let currentAbortController = null;

async function runThinkingPipeline() {
  // Cancel any in-flight pipeline
  if (currentAbortController) {
    currentAbortController.abort();
  }
  currentAbortController = new AbortController();
  const signal = currentAbortController.signal;

  // Pass signal to all fetch calls
  const response = await fetch(url, { method: "POST", headers, body, signal });
  
  // Check after each step
  if (signal.aborted) return;
  
  // ... continue pipeline
}

Also add isRunning guard to runRefinement().

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions