Mobile background-sync failure monitoring

Background sync, refresh, prefetch jobs fail silently and degrade UX. Group failures by task_name and network_type to find the patterns.

Updated · published

markdown for agents →

difficulty intermediate · time to value 2 minutes · execution on demand

Start from this

Are background tasks (sync, refresh, prefetch) failing more often this week? Group by task_name and network_type (wifi vs cellular).

Why this matters

Background failures are the worst kind of bug: invisible until the user notices the symptom. The sync didn’t run. The image prefetch failed. The token refresh silently expired. The user opens the app, sees stale data, and uninstalls. No crash, no error report — just a slow drip of churn.

These bugs cluster around two dimensions:

  1. task_name: which job is failing (sync_messages, prefetch_images, refresh_token, upload_pending)
  2. network_type: wifi-vs-cellular reveals timeout-tuning bugs and DNS-fallback misses

Grouping by both is the diagnostic. “All sync_messages on cellular” → cellular timeout is too aggressive. “All prefetch_images everywhere” → CDN regression.

Agentry runs the cross-tab in one prompt. The signal usually surfaces in 30 seconds.

What you get

  • A failure-rate matrix: task_name × network_type
  • Week-over-week delta so you can spot regressions vs chronic noise
  • The top open cases tied to the worst-performing task

Walk through it

You

Are background tasks failing more this week? Group failures by task_name and network_type.

Agent

First the matrix.

Endpoint: POST /v1/projects/:project_id/analytics/query
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fanalytics%2Fquery&method=post
Custom HogQL goes in the OpenAPI-defined `query` field.
Concept fields:
  query: "
    select
      properties.task_name as task,
      properties.network_type as net,
      countIf(event = 'background_task_started') as started,
      countIf(event = 'background_task_failed') as failed,
      countIf(event = 'background_task_failed') / countIf(event = 'background_task_started') as fail_rate
    from events
    where event in ('background_task_started', 'background_task_failed')
      and timestamp > now() - interval 7 day
    group by task, net
    having started > 100
    order by fail_rate desc
  "
{
  "rows": [
    { "task": "sync_messages",   "net": "cellular", "started":  84210, "failed": 24180, "fail_rate": 0.287 },
    { "task": "prefetch_images", "net": "wifi",     "started":  42100, "failed":  8420, "fail_rate": 0.200 },
    { "task": "prefetch_images", "net": "cellular", "started":  21430, "failed":  6210, "fail_rate": 0.290 },
    { "task": "refresh_token",   "net": "cellular", "started":  62800, "failed":  3820, "fail_rate": 0.061 },
    { "task": "sync_messages",   "net": "wifi",     "started": 168900, "failed":  4180, "fail_rate": 0.025 },
    { "task": "refresh_token",   "net": "wifi",     "started":  82400, "failed":   820, "fail_rate": 0.010 },
    { "task": "upload_pending",  "net": "wifi",     "started":  18400, "failed":   180, "fail_rate": 0.010 }
  ]
}
Agent

Now the week-over-week.

Endpoint: POST /v1/projects/:project_id/analytics/query
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fanalytics%2Fquery&method=post
Custom HogQL goes in the OpenAPI-defined `query` field.
Concept fields:
  query: "
    select
      properties.task_name as task,
      properties.network_type as net,
      countIf(event = 'background_task_failed' and timestamp > now() - interval 7 day) /
        nullIf(countIf(event = 'background_task_started' and timestamp > now() - interval 7 day), 0) as this_week,
      countIf(event = 'background_task_failed' and timestamp between now() - interval 14 day and now() - interval 7 day) /
        nullIf(countIf(event = 'background_task_started' and timestamp between now() - interval 14 day and now() - interval 7 day), 0) as last_week
    from events
    where event in ('background_task_started', 'background_task_failed')
      and timestamp > now() - interval 14 day
    group by task, net
    having this_week > 0.05 or last_week > 0.05
    order by this_week - last_week desc
  "
{
  "rows": [
    { "task": "sync_messages",   "net": "cellular", "this_week": 0.287, "last_week": 0.040 },
    { "task": "prefetch_images", "net": "wifi",     "this_week": 0.200, "last_week": 0.020 },
    { "task": "prefetch_images", "net": "cellular", "this_week": 0.290, "last_week": 0.022 },
    { "task": "refresh_token",   "net": "cellular", "this_week": 0.061, "last_week": 0.058 }
  ]
}
Agent

Two regressions, both this week:

  1. prefetch_images (wifi AND cellular): jumped from 2% to 20-29%. Same on both networks → not a network issue → CDN regression. Let me check the open cases.
  2. sync_messages (cellular only): jumped from 4% to 28.7%. Wifi is fine. Cellular-specific → likely a timeout tuning issue. Either you tightened a timeout in the last release or the upstream got slower.
Endpoint: GET /v1/projects/:project_id/cases
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fcases&method=get
Use query parameters, not a JSON body.
Query/filter concept:
  status: "open",
  matching: "prefetch|image|sync_message|timeout",
  window_hours: 168
{
  "cases": [
    {
      "case_id": "c_bg_1",
      "fingerprint": "f_img_a",
      "error_type": "HttpError",
      "message": "GET https://cdn.example.com/images/* returned 503",
      "events_count": 14630,
      "users_affected": 8200,
      "first_seen_at": "2026-05-13T11:00:00Z"
    },
    {
      "case_id": "c_bg_2",
      "fingerprint": "f_sync_a",
      "error_type": "SocketTimeoutException",
      "message": "Failed to connect to sync.api.example.com:443 after 5000ms",
      "events_count": 24180,
      "users_affected": 11420,
      "first_seen_at": "2026-05-13T15:30:00Z"
    }
  ]
}
Agent

Both cases first appeared May 13 — same window the prod deploy went out for v4.2.0.

  1. CDN 503s: 14,630 events across 8,200 users. The CDN itself or a config push on it. Talk to whoever owns the CDN config — check if a rate-limit/auth rule was added.
  2. Cellular sync timeouts: 24,180 events across 11,420 users. The 5000ms timeout in sync_messages is too aggressive for cellular. The Android default is 30s for a reason. Either bump the timeout for cellular, or implement an exponential-retry strategy.

Both are fixable in a 4.2.1 patch. Want me to open a triage note in Agentry memory so it’s pinned to these cases?

The output

Background task failures (7d) — cross-tab task × network

                       WIFI fail %   CELLULAR fail %   WoW Δ (cellular)
sync_messages              2.5%        28.7% ★★★         +24.7 pp
prefetch_images           20.0% ★      29.0% ★★          +27.0 pp
refresh_token              1.0%         6.1%              +0.3 pp
upload_pending             1.0%         (no data)         —

Two regressions detected (both started May 13, deploy 4.2.0):

  1. prefetch_images: 2% → 20-29% (BOTH networks)
     Root: CDN 503s on https://cdn.example.com/images/*
     Case: c_bg_1 (14,630 events, 8,200 users)

  2. sync_messages: cellular 4% → 28.7% (wifi unaffected)
     Root: SocketTimeoutException at 5000ms — too aggressive for cellular
     Case: c_bg_2 (24,180 events, 11,420 users)

Recommended actions:
  - CDN config audit (rate limit / auth rule pushed recently?)
  - Bump cellular timeout to 30s OR add exponential retry
  - Ship 4.2.1 with both fixes

Setting it up

Two events per background task: started and failed. The network_type property is the diagnostic axis.

// Android WorkManager wrapper
class TrackedWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
  override suspend fun doWork(): Result {
    postAnalytics("background_task_started", mapOf(
      "task_name"    to taskName,
      "network_type" to currentNetworkType(),
      "app_version"  to BuildConfig.VERSION_NAME
    ))
    return try {
      doActualWork()
      Result.success()
    } catch (err: Throwable) {
      // analytics: count the failure
      postAnalytics("background_task_failed", mapOf(
        "task_name"    to taskName,
        "network_type" to currentNetworkType(),
        "error_type"   to err.javaClass.simpleName,
        "app_version"  to BuildConfig.VERSION_NAME
      ))
      // logs: capture the stack
      postLog(mapOf(
        "message"      to err.message,
        "stack"        to err.stackTraceToString(),
        "error_type"   to err.javaClass.simpleName,
        "properties"   to mapOf("task_name" to taskName, "network_type" to currentNetworkType())
      ))
      Result.retry()
    }
  }
}

Where postLog is your /v1/logs/ helper:

async function postLog(payload: Record<string, unknown>) {
  await fetch(`https://api.agentry.sh/v1/logs/`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.AGENTRY_SERVER_API_KEY}`,
      "Content-Type": "application/json",
      "User-Agent": "myapp-android/4.2.0",  // REQUIRED — Cloudflare 403s default UAs
    },
    body: JSON.stringify(payload),
  });
}

Variations

  • “Same cross-tab but per app_version — is the regression isolated to 4.2.0?”
  • “Median retry attempts before success per task — long tails indicate flaky infrastructure.”
  • “Per-region cellular failure rate — sometimes a single carrier’s gateway is the issue.”
  • “On demand, find any task × network pair above 15% failures over one hour and draft a #mobile-oncall summary for review.”

Adapt this workflow in your own agent.

Ask your agent to map the starter prompt to your saved signal map, live events, and the current HTTP API before answering.

Agentry gives coding agents production context from errors, product analytics, and deploys through one HTTP API. Fetch and read https://agentry.sh/install.md completely, then install Agentry's full first-time telemetry baseline in this repository. I authorize device-code login, saving the owner key only to ~/.agentry/credentials.json, creating one Agentry project for this repository, and read-only inspection of shipped source plus hidden CI/provider configuration. Deeply analyze the product's business logic and complete value flow before choosing telemetry. Save the complete source-backed plan and show me its exact contents and hash. Until I explicitly approve that plan or request a replacement, do not edit app/CI source, place runtime/CI credentials, commit, exercise product traffic, start proof, push, or deploy. After approval, I authorize only the reviewed targets: place the required scoped browser/server/CI credentials through the established environment or secret mechanism, preserve existing telemetry, implement and test the baseline, commit it, push that reviewed commit when the shipped CI/provider path requires it, exercise safe proof paths with test/non-customer data, and perform one deployment through the reviewed shipped CI/provider path. Ask first if proof would charge money, contact a third party, change real customer data, or require new external access. After the plan is saved, immediately before every onboarding state-changing POST, GET current onboarding state, perform only its single returned next_action, then read state again; do not batch or infer later stages. Continue until status is verified, installation_complete is true, and next_action is null. Keep all secrets, source snapshots, proof markers, and scratch files outside the repository.

+ Full access
5.5 Extra High
  1. 1. Open your repo in Codex, Claude Code, Cursor etc.
  2. 2. Paste the install prompt.
  3. 3. Your agent reads the install doc and shows you an implementation plan for approval.