Skip to content
Agentmart
Submit a tool

background-tasks

Builds expo-background-task jobs: idempotent, budgeted runs.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-background-tasks

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Builds expo-background-task jobs: idempotent, budgeted runs. Use when adding periodic sync, TaskManager.defineTask, leaving expo-background-fetch, or a background task never runs. Not for reminders (local-notifications).

Use cases: productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/background-tasks

What's inside

Skill name
background-tasks
Description
Builds expo-background-task jobs: idempotent, budgeted runs. Use when adding periodic sync, TaskManager.defineTask, leaving expo-background-fetch, or a background task never runs. Not for reminders (local-notifications).
License
MIT
Instructions
239 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (55)

  • SKILL.md
  • shared.json · assets/
  • background-environment.ts · examples/
  • background-jobs.ts · examples/
  • index.ts · examples/
  • open-background-data.ts · examples/
  • sync-outbox-job.test.ts · examples/
  • sync-outbox-job.ts · examples/
  • use-background-registration.test.ts · examples/
  • use-background-registration.ts · examples/
  • use-sync-outbox-on-foreground.test.ts · examples/
  • use-sync-outbox-on-foreground.ts · examples/
  • api-and-config.md · references/
  • checker-rules.md · references/
  • job-design.md · references/
  • platform-behaviour.md · references/
  • testing-and-device-proof.md · references/
  • check-background-tasks.mjs · scripts/
  • check-lib.mjs · scripts/
  • background-scan.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • background-run-log-port.ts · templates/src/services/background-run-log/
  • fake-background-run-log.test.ts · templates/src/services/background-run-log/
  • fake-background-run-log.ts · templates/src/services/background-run-log/
  • kv-background-error-log-adapter.test.ts · templates/src/services/background-run-log/
  • kv-background-error-log-adapter.ts · templates/src/services/background-run-log/
  • kv-background-run-log-adapter.test.ts · templates/src/services/background-run-log/
  • kv-background-run-log-adapter.ts · templates/src/services/background-run-log/
  • background-environment.ts · templates/src/services/background-tasks/
  • background-job.test.ts · templates/src/services/background-tasks/
  • background-job.ts · templates/src/services/background-tasks/
  • background-jobs.test.ts · templates/src/services/background-tasks/
  • background-jobs.ts · templates/src/services/background-tasks/
  • background-tasks-port.ts · templates/src/services/background-tasks/
  • define-background-tasks.test.ts · templates/src/services/background-tasks/
  • define-background-tasks.ts · templates/src/services/background-tasks/
  • expo-background-tasks-adapter.android.test.ts · templates/src/services/background-tasks/
  • expo-background-tasks-adapter.test.ts · templates/src/services/background-tasks/
  • expo-background-tasks-adapter.ts · templates/src/services/background-tasks/
  • fake-background-tasks.test.ts · templates/src/services/background-tasks/
  • fake-background-tasks.ts · templates/src/services/background-tasks/
  • reconcile-background-tasks.test.ts · templates/src/services/background-tasks/
  • reconcile-background-tasks.ts · templates/src/services/background-tasks/
  • task-executor.test.ts · templates/src/services/background-tasks/
  • task-executor.ts · templates/src/services/background-tasks/
  • job-test-kit.test.ts · templates/test/background/
  • job-test-kit.ts · templates/test/background/
  • app.config.ts · tests/bases/app/
  • native-config.json · tests/bases/app/contracts/
  • package.json · tests/bases/app/
  • tsconfig.json · tests/bases/app/
  • CASES.json · tests/fixtures/check-background-tasks/
  • SUITE.json · tests/fixtures/check-background-tasks/
  • EXPECT.txt · tests/fixtures/check-background-tasks/good/

SKILL.md (first lines)

# Background tasks

Work outside the foreground is a short, idempotent job that the system may run, cut short, repeat or
never run at all, on iOS (BGTaskScheduler) and Android (WorkManager) alike. Every task is defined
where a headless launch can find it, goes through one runner that applies a budget and never throws,
is scheduled through a port with one explicit interval, also runs on a foreground path, and leaves a
durable run record. A zero-dependency checker proves the parts tsc, ESLint and Jest cannot see.

## Rules that must hold

Verified on 2026-10-14 against `expo-background-task` and `expo-task-manager` 57.0.21 (source,
introspection and the Expo SDK 57 pages; device behaviour is from the platform docs, see
references/platform-behaviour.md).

1. **Use expo-background-task, and list its config plugin.** No `expo-background-fetch` (deprecated,
   no patches); `'expo-background-task'` is in the app config plugins and the native snapshot shows
   `UIBackgroundModes` `processing` and the identifier `com.expo.modules.backgroundtask.processing`.
   Why: prebuild does not apply this plugin on its own in SDK 57 (introspected), and without
   `processing` every registration rejects on an iOS device. (`no-background-fetch`,
   `background-plugin`, `background-plist`)
2. **Define every task at module scope, in a file the app entry imports.** `TaskManager.defineTask`
   lives only in `define-background-tasks.ts`, at top level, and `index.ts` (`package.json` `main`)
   imports it after the Intl polyfills and before `'expo-router/entry'`. Why: an Android run in a
   stopped process evaluates the bundle with the headless loader and renders no route, and a task that
   fires undefined is unregistered for good. (`define-task-top-level`, `define-task-entry`)
3. **A job's name is one literal, declared once.** `defineBackgroundJob({ name: '<area>.<job>' })`,
   kebab-case, unique; every other call uses `job.name`. Why: a literal at a call site drifts from the
   definition, and two definitions of one name overwrite each other. (`job-name`, `task-name-literal`)
4. **Every executor is `toTaskExecutor(job, createBackgroundEnvironment)`**, for notification and
   location tasks too (their `data` and `error` reach the job as `input` and `error`). Why: the task
   manager only `console.error`s a rejected executor; the runner turns a throw into a recorded
   `crashed` run with its cause in the durable background error log (kv-store, read later by the
   debug screen), records it as a `background` run, disposes the environment, maps the end to
   `BackgroundTaskResult` and never rejects. (`executor-through-runner`)
5. **Every job has a literal budget of at most 25 s and asks `shouldStop()` before each step.** Why:
   the system may end a run at any moment (iOS expiry, Android stopping the worker) and 25 s is this
   library's design default, and only the job can stop cleanly between steps; the runner aborts the signal at the budget and records `timed-out`.
   (`job-budget`, runner tests)
6. **Jobs are idempotent and resumable, and their test proves both.** Idempotency keys on every
   external effect, state transitions instead of increments, one commit per step, progress in storage;
   the sibling test calls `runJobTwice` and `runJobUntilAborted`. Why: runs repeat (expiry after the
   server applied a change) and die between any two instructions. (`job-tested`)
7. **Jobs run without React.** A job imports no React, React Native, router, store, feature, ui or
   context module; it receives ports and repositories in `BackgroundDeps`, created per run by
   `createBackgroundEnvironment()`. Why: a headless run has no providers, and on iOS the root layout
   may not have created its services when the task fires. (`job-no-react`)
8. **A background run never migrates, and gets the database from the data layer.** The data layer's
   background open path opens its own connection only when the version equals the latest and returns
   repositories (never the driver); otherwise the job returns `deferred` (`schema-behind`). Why: a
   migration there can be cut mid-step, races the foreground start, and fails where nobody sees it;
   a shared connection closed in `dispose` would close the app's. (`job-no-migrate`)
9. **Only the adapter and the registry import the SDKs.** App code schedules through
   `BackgroundTasksPort`; tests read the SDK with `jest.requireMock`/`requireActual`. Why: one file
   per vendor surface, and Jest never touches native code. (`vendor-import-confined`)
10. **One explicit interval of at least 15 minutes, registered by `reconcileBackgroundTasks` after
    startup.** The reconcile honours `availability` and the user's setting, removes names the build no
    longer defines and re-registers a changed interval. Why: all jobs share one system worker (the last
    registration sets the interval), the defaults differ (12 h iOS, 24 h Android), and
    `registerTaskAsync` keeps an existing registration. (`minimum-interval`, reconcile tests)
11. **Every job also runs on a foreground path, through the same runner, as a `foreground` run.** App
    active, after a write, pull to refresh: `runBackgroundJob(job, environment, { trigger:
    'foreground' })`. Why: the system may never grant a background run (restricted device, Low Power
    Mode, an iOS force-quit), the runner's single flight stops the two from overlapping, and the run
    log keeps the two triggers apart. (`job-foreground-path`)
12. **The test trigger is debug-only, and the device proof is done.** `triggerTaskWorkerForTestingAsync`
    only through `triggerBackgroundRunForTesting()` (returns false unless `__DEV__`); every new job is
    forced once on a named Android emulator (`adb -s`) and on an iOS device, and
    `lastRun(name, 'background')` shows a run newer than the trigger (a `foreground` record never
    counts). Why: in a release build the trigger does nothing (the SDK resolves false outside
    `__DEV__`), so a debug control left in silently does nothing; the iOS simulator has no background
    tasks, and only a device proves the headless path and the configuration. (`trigger-dev-only`, references/testing-and-device-proof.md)

## Workflow

1. **Decide whether it is background work at all**: read
   [references/api-and-config.md](references/api-and-config.md) section 1. A reminder at a time is a
   local notification, a server event is a push, freshness on open is the foreground path.
2. **Learn the platform limits** before designing: [references/platform-behaviour.md](references/platform-behaviour.md)
   (one worker, network required, budgets, headless runs, force-quit, intervals, simulator).
3. **Install and configure**: `npx expo install expo-background-task expo-task-manager`; add
   `'expo-background-task'` to the app config plugins; regenerate the native snapshot
   (expo-prebuild-and-config-plugins); add the contract entries and ESLint vendor lines of
   api-and-config.md section 6. Remove `expo-background-fetch` if present (section 8).
4. **Copy the templates** at the paths their first lines name: `templates/src/services/background-tasks/`,
   `templates/src/services/background-run-log/`, `templates/test/background/`. They pass tsc, ESLint
   and Jest in the reference app.
5. **Wire the entry**: add `import '@/services/background-tasks/define-background-tasks';` to
   `index.ts` after the Intl polyfills, before `'expo-router/entry'` (`examples/index.ts`). An app
   whose `main` is `expo-router/entry` gets an `index.ts` first.
6. **Write the job** from [references/job-design.md](references/job-design.md), imitating
   `examples/sync-outbox-job.ts`: `defineBackgroundJob`, a literal name and budget, a bounded batch,
   `shouldStop()` before each step, outcomes with kebab-case reasons.
7. **Test the job first** with `test/background/job-test-kit.ts` (`runJobTwice`, `runJobUntilAborted`,
   the job's own failure modes), imitating `examples/sync-outbox-job.test.ts` and
   [references/testing-and-device-proof.md](references/testing-and-device-proof.md) section 2.
8. **Register it**: add it to `BACKGROUND_JOBS` and its dependencies to `BackgroundDeps` and
   `createBackgroundEnvironment()` (`examples/background-jobs.ts`, `examples/background-environment.ts`).
   A job that needs the app database gets repositories from the data layer's background open path
   (`examples/open-background-data.ts` at `src/data/open-background-data.ts`: own connection, current
   schema only, closed in `dispose`), listed in `contracts/persistence.json` `sqlAllowed`
   (api-and-config.md section 6).
9. **Schedule after startup**: call `reconcileBackgroundTasks` from a hook the root runs once startup
   is ready and when the setting changes (`examples/use-background-registration.ts`, with its test);
   show `restricted` as unavailable in the setting row.
10. **Add the foreground path**: run the same job through `runBackgroundJob(job, environment,
    { trigger: 'foreground' })` on app active or after a write, importing the job or the job list
    (`examples/use-sync-outbox-on-foreground.ts`, with its test).
11. **Run the checks**: `npm run typecheck`, `npm run lint`, `npx jest <changed tests> --ci`, then
    `node ${CLAUDE_SKILL_DIR}/scripts/check-background-tasks.mjs .`. Fix each `FAIL` line
    ([references/checker-rules.md](references/checker-rules.md) explains every rule) and rerun until
    `RESULT: PASS`.
12. **Prove it on devices**: force a run on a named Android emulator with `adb -s <serial>` (also with
    the process killed) and on an iOS device from the debug menu or LLDB; read the newest
    `background` record of each job (testing-and-device-proof.md sections 3 to 5). The developer signs
    and installs the iOS build.

## Definition of done

- [ ] `expo-background-task` and `expo-task-manager` installed with `npx expo install`; no `expo-background-fetch`.

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (11 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

background-tasks skill. Agent Mart. https://agentmart.dev/skills/zakariaf-background-tasks (last updated 2026-10-24)