background-tasks
Builds expo-background-task jobs: idempotent, budgeted runs.
Skill by zakariaf
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.
About
Use cases: productivity
Reviews
Write a reviewNo 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
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
ci-pipeline-and-gates
zakariaf
Builds GitHub Actions CI for Expo apps - verify on every PR, SHA-pinned actions, pinned runners and Node, device lanes, manual EAS releases.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
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
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
Cite this page
background-tasks skill. Agent Mart. https://agentmart.dev/skills/zakariaf-background-tasks (last updated 2026-10-24)