Fastly Compute Async Request Reliability
SkillCommunicationFix silent failures in Fastly Compute@Edge when using send_async for fire-and-forget requests. Use when: (1) Background tasks (migrations, webhooks, notifications) triggered from Compute never complete, (2) send_async PendingRequest is immediately dropped, (3) Fire-and-forget pattern works locally but fails in production, (4) An async trigger "logs as triggered" but the target service never receives the request (especially after renaming backend constants — the URL hostname is cosmetic, the backend name determines routing, so a global rename can silently reroute calls to the wrong service). Also covers silent backend-not-found errors when FALLBACK_BACKENDS or other backend constants reference names not configured in the Fastly dashboard.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Fastly Compute Async Request Reliability skill
What this skill tells your AI
The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/fastly-compute-async-request-reliability/SKILL.md and read by ahel’s review.
Problem
Fire-and-forget HTTP requests from Fastly Compute@Edge using send_async silently fail
because the worker process can terminate before the async request reaches the backend.
Additionally, backend names referenced in code but not configured in the Fastly dashboard
cause silent failures that are easy to miss.
Context / Trigger Conditions
- Background migration, webhook, or notification triggered via
req.send_async(backend) - The
PendingRequestreturned bysend_asyncis immediately dropped (not awaited) - The main response is sent to the client, causing the Compute worker to terminate
- Error is swallowed with
let _ = ...ormatch ... { Err(_) => ... } - Works in local testing (
fastly compute serve) but fails in production - Backend name in code doesn't match any backend in
fastly backend list --service-id
Solution
1. Use synchronous send() instead of send_async() for critical operations
// BAD: Fire-and-forget — worker terminates before request completes
match req.send_async(BACKEND) {
Ok(_pending) => {
// PendingRequest dropped here — request likely never completes!
Ok(())
}
Err(e) => { /* ... */ }
}
// GOOD: Synchronous send — waits for response
match req.send(BACKEND) {
Ok(resp) => {
let status = resp.get_status();
if status.is_success() {
eprintln!("[MIGRATE] Success for {}", hash);
} else {
eprintln!("[MIGRATE] Backend returned {}", status);
}
Ok(())
}
Err(e) => {
eprintln!("[MIGRATE] Failed: {}", e);
Ok(()) // Don't fail the main request
}
}
2. When synchronous is too slow, use a caching layer
If a VCL caching layer fronts Compute (service chaining), the extra latency from synchronous send only affects cache misses. Subsequent requests hit the cache. This makes synchronous send acceptable for operations like migration triggers.
3. Always verify backend existence
# List backends configured on the service
fastly backend list --service-id YOUR_SERVICE_ID --version latest
# Add missing backend
fastly backend create --service-id YOUR_SERVICE_ID --version latest --autoclone \
--name cdn_divine --address cdn.divine.video --port 443 \
--use-ssl --ssl-sni-hostname cdn.divine.video --override-host cdn.divine.video
Check that every backend name in your Rust code (req.send("backend_name")) has a
corresponding entry in the Fastly dashboard. The fastly.toml [local_server.backends]
section is for local dev only — it does NOT create production backends.
4. Beware: the URL hostname is cosmetic — the backend name decides routing
In Fastly Compute, req.send(backend_name) / req.send_async(backend_name) ignores the
URL's hostname for routing purposes. The backend's dashboard configuration (address,
override_host, TLS SNI) determines where the request actually lands. This means two
different Cloud Run services that happen to share a backend constant will both deliver
to whichever address that one backend is wired to — and the call site that looked
correct because its URL said service-A.run.app will actually hit service-B.
This bites hardest after a global rename. Example: a refactor replaces
CLOUD_RUN_BACKEND with UPLOAD_SERVICE_BACKEND across the whole file via sed-style
search-and-replace. Most call sites were legitimately targeting the upload service, so
they still work. But one call site was doing POST https://divine-transcoder-XXXX.run.app/transcode
and the rename points it at the upload service backend — the request gets a nginx 404
from the upload service, send_async returns Ok (backend accepted the connection), and
the eprintln!("[HLS] Triggered on-demand transcoding for {}", hash) log line is a
false positive. The transcoder never sees the request. Videos pile up in an "in
progress" state for days.
How to catch this class of bug:
-
When you see a rename of a backend constant, grep for every call site and verify the URL hostname matches what the new backend is configured to route to:
grep -n "send(\|send_async(" src/*.rs grep -n "BACKEND: &str" src/*.rs # find constant definitionsAny call site where
format!("https://{}/...", SOMETHING_ELSE)doesn't match the backend's dashboardaddress/override_hostis a silent misroute. -
Probe the "wrong" destination directly from the outside with curl using a synthetic payload. If you get a 404 or 405 from nginx/the wrong service, you've found a misroute. Example:
curl -X POST https://upload.divine.video/transcode -H 'Content-Type: application/json' -d '{"hash":"0"*64}' # HTTP 404 <- wrong service, backend misrouted -
If you own more than one Cloud Run service, define one backend per service (
transcoder_backend,upload_service,transcriber_backend) and never collapse them just because both are*.run.app. Theoverride_hoston each backend pins its destination regardless of URL. -
Switch fire-and-forget triggers that you care about to synchronous
send()at least during investigation — a real non-2xx response from the wrong service makes the bug visible immediately instead of hiding behindsend_async.
5. Never silently swallow backend errors for important operations
// BAD: Silent failure
let _ = trigger_migration(&hash, &source);
// GOOD: Log the error even if you don't fail the request
if let Err(e) = trigger_migration(&hash, &source) {
eprintln!("[MIGRATE] Failed for {}: {}", hash, e);
}
Verification
- Check Compute logs for the migration/webhook success messages
- Verify the side effect actually happened (e.g., blob exists in GCS after migration)
fastly backend list --service-id ID --version latestshows all expected backends
Example
In Divine Blossom, the on-demand migration from Bunny CDN to GCS never worked because:
cdn_divinebackend was never configured in the Fastly dashboard (only infastly.toml)send_asyncfor the Cloud Run migration request was dropped before completion- Both errors were silently swallowed with
let _ = ...
Fix: Added cdn_divine backend via CLI, changed send_async to send, added logging.
Notes
fastly.toml[local_server.backends]only configures backends forfastly compute serve- Production backends must be configured via dashboard or CLI (
fastly backend create) - In Fastly Compute, once the main response body starts streaming to the client, the worker can be terminated at any time — async requests in flight may be cancelled
- If you need true fire-and-forget, consider calling an external queue (Cloud Tasks, PubSub) instead of direct HTTP
Signals
- GitHub stars
- 265
- Forks
- 55
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
fastly-compute-async-request-reliability- Source
- github.com/divinevideo/divine-mobile