abort fetch after submission: keep the remote operation recoverable
·
Closing a browser tab or aborting Fetch stops a local request. It does not establish that a generation was never accepted. Design your application around an explicit uncertainty state whenever the connection ends before the accepted response is saved. This is especially important when a navigation event aborts a request that may already have reached the server.
Separate three different actions
Submission creates remote work. Observation reads its state. Cancellation is a distinct authenticated operation against a known job. An AbortController affects the client connection; it is not a substitute for the cancellation endpoint. The current Genace cancellation implementation changes local job state and accounting without promising to stop an upstream render physically.
Create an application operation record before dispatch, with states such as prepared, submitting, accepted, and uncertain. Those labels belong to your application, not Genace’s job state machine. Record the selected model, request fingerprint, and creation time so an operator can investigate a connection failure without relying on browser memory.
export function classifySubmission({ response, body, transportError }) {
if (response?.ok && typeof body?.job_id === 'string' && body.job_id.trim()) {
return { localState: 'accepted', jobId: body.job_id };
}
if (response?.status === 502 &&
['UPSTREAM_SUBMISSION_FAILED', 'UPSTREAM_SUBMISSION_UNCERTAIN'].includes(body?.error?.code) &&
typeof body.error.job_id === 'string' && body.error.job_id.trim()) {
return {
localState: body.error.code === 'UPSTREAM_SUBMISSION_FAILED' ? 'inspect_failure' : 'uncertain',
jobId: body.error.job_id,
};
}
if (transportError) return { localState: 'uncertain' };
return { localState: 'uncertain', status: response?.status };
}
This classifier is a deliberately conservative local policy, not a complete envelope validator. A generic 500 or 502 and a successful response missing a usable identifier all remain uncertain. A production client may separately recognize verified pre-submission rejection envelopes, but must not treat an unrecognized response as proof that nothing was created.
Preserve a known identifier immediately
Once a successful POST returns job_id, persist it before updating the interface. If an HTTP 502 provides error.job_id, preserve that local identifier too and inspect the existing job and its accounting. Use GET /api/v1/jobs/{id} afterward; its snapshot identifies the job with id.
If no identifier arrived, do not manufacture one from your application operation ID. A local request ID and a Genace job ID identify different resources. Keep the operation uncertain and reconcile using your saved request context and operational support channels. There is no documented creation idempotency key that turns an automatic repeated POST into the same generation.
Stop observation without falsifying state
After acceptance, a user may leave the screen while a backend collector continues. Aborting that screen’s GET or SSE request should record that observation stopped. It should not rewrite your saved remote state to failed or canceled. Another session can resume using the stored identifier.
If the user intentionally chooses cancellation, send the explicit cancellation request only after checking ownership and your application’s policy. Handle a job that completed before the cancellation arrived. A terminal response may describe success rather than the canceled state your interface expected.
Test the navigation boundary
Simulate aborts before dispatch, after dispatch but before response delivery, and after the accepted ID is persisted. Assert that only the last case can resume automatically by ID, while the middle case remains visibly uncertain. Confirm no navigation cleanup handler sends a second POST.
See explicit cancellation, resuming jobs, and deadline propagation for the surrounding lifecycle.
Unknown submission outcomes need reconciliation
Transport failures and malformed acceptance responses use
UPSTREAM_SUBMISSION_UNCERTAIN, preserve the queued reservation, and do not
trigger another creation request. Nonterminal jobs with an uncertain marker
or either missing upstream handle cannot be canceled into a refund: the route
returns HTTP 409 SUBMISSION_RECONCILIATION_REQUIRED, including when saving
the uncertainty marker failed. Legacy failure codes without rejection evidence
need manual investigation, not bulk balance changes. Read
the unknown-submission reference.
Continue with code and integration
Where these facts come from
- codebase: src/ai/providers/fal-client.ts
- Editorial keyword hypothesis; search volume not measured
- https://fetch.spec.whatwg.org/
- codebase: src/ai/jobs/service.ts
- codebase: src/app/api/v1/jobs/[id]/cancel/route.ts