Skip to content

AI_RESPONSE can double-post under timeout-retry #176

Description

@NathanTarbert

Pre-existing in main, not introduced by #172. Closed by #170's delta. Filed so it stays visible while #172 is in production.

runWithTimeout in packages/outpost/queue/src/worker.ts is a Promise.race between the handler and a timer. When the timer wins, the race resolves — but the handler keeps running. Nothing cancels it.

AI_RESPONSE has a 120s timeout (apps/worker/src/index.ts) over a deliberately slow pipeline (Pathfinder retrieval, generation, confidence scoring, groundedness gate), so hitting it is not exotic.

The sequence

  1. Attempt 1 starts, runs past 120s. The race resolves as a timeout; the handler continues.
  2. The worker marks the job for retry; attempt 2 starts roughly a second later.
  3. Attempt 2 loads the ticket's messages before attempt 1 reaches its message.create, so it sees no prior AI response and passes the re-answer gate.
  4. Both attempts generate, and both post to the platform.

Two AI answers in one thread — the exact behaviour #172 exists to prevent, reachable without any duplicate inbound event.

Why the gate alone cannot close it

The gate is a check-then-write against a snapshot. Two concurrent readers both see "no response yet." No amount of care inside the handler fixes that; it needs the database to arbitrate.

#170's delta does exactly that: a unique index on Message(ticketId, responseKey), claimed via message.create before any platform post-back, so the loser catches a narrowly-scoped P2002 and returns without posting. Plus Job.claimToken fencing so a timed-out attempt cannot write back over the attempt that replaced it.

Two things worth fixing regardless of #170

  • runWithTimeout doesn't cancel. Even with the uniqueness constraint, a timed-out handler keeps burning a pipeline slot and model tokens to produce a response that will be discarded. An AbortSignal threaded into the handler would stop the work rather than just ignoring it.
  • 120s may be too tight for this pipeline. If timeouts are routine rather than exceptional, the retry storm is the real problem and the double-post is a symptom.

Exposure while #172 is in production

Needs an AI_RESPONSE job to exceed 120s. Worth checking how often that actually happens before judging severity — [AI Response] Ticket … latency= log lines carry the number.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: aiAI response pipeline, groundedness, confidenceroadmapTracked on the Outpost roadmaproadmap: nowRoadmap horizon: V1 launch critical path / safety

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions