// OPENBOX DOCS

Background jobs

Observable states, retry limits, replacement, cancellation, and volatility.

Background work in OpenBoxGL runs through the durable Operation Service (pkg/state/operations.py). Operations are persisted to disk in operations.json, surviving application restarts and supporting progress streaming, cancellation, retries, and resume flows.

The operation object

Every submitted operation is recorded with these fields:

KeyMeaning
operation_idUnique hex identifier
typeDotted operation type (see supported types below)
statequeued, running, cancelling, done, partial, error, cancelled, or interrupted
created_atUTC ISO timestamp
started_atStart timestamp
finished_atTerminal timestamp
progressCurrent progress object (current, total, unit, message)
errorError message on failure
metadataContext-specific parameters and batch identifiers

Supported operation types

Jobs carry a dotted type, not a legacy queue name: setup.scan, setup.revalidate, setup.commit, metadata.db_sync, metadata.match_preview, metadata.apply, media.bulk_download, media.cleanup, media.memories_import, emulator.install, emulator.update, gameyfin.install, saves.scan, saves.backup, library.backup, library.restore, library.export, screenscraper.match, screenscraper.apply, steamgrid.match, steamgrid.apply, storefront.auto_import, clips.reel, cloud.sync, updater.install. Legacy job names (for example library-export, screenscraper-match, auto-import) map to these types with the matching retry policy, so export, SteamGridDB, ScreenScraper, auto-import, and reel jobs no longer masquerade as setup.scan. At most MAX_OPERATIONS=100 operations are retained (plus 30-day retention for finished runs).

Workers execute on a managed ThreadPoolExecutor with 4 concurrent slots (openbox-job-*). Live status and incremental events stream to the frontend over Server-Sent Events (SSE) via /api/events.

States and lifecycle transitions

queued -> running -> cancelling -> cancelled
                  -> done
                  -> partial
                  -> error
(on restart)      -> interrupted -> (resumed or retried)
  • queued: Submitted and awaiting execution slot.
  • running: Actively executing in a worker thread.
  • cancelling: User requested cancellation; worker is safely terminating.
  • cancelled: Worker terminated early without side-effect leaks.
  • done: Operation completed successfully.
  • partial: Operation completed with some skipped or non-fatal item errors.
  • error: Unrecoverable failure occurred.
  • interrupted: Process restart occurred while job was in flight. Can be resumed or retried via POST /api/v2/jobs/resume or POST /api/v2/jobs/retry (both accept job_id in the JSON body).

Durability & Recovery (operations.json)

Unlike legacy in-memory job queues, all operations are persisted atomically to operations.json in the data directory.

  • On startup, any operation previously left in running or queued state is automatically marked interrupted.
  • The Activity drawer (#activityButton) displays live and historical operations.
  • Interrupted jobs can be cleanly retried or resumed without duplicating finished work.

Observability & Endpoints

  • GET /api/v2/jobs: Lists active, pending, and recent operations.
  • GET /api/v2/jobs/items: Paginated list of operation items for a specific job (?job_id=).
  • POST /api/v2/jobs/cancel: Cancels an in-flight operation (accepts job_id in JSON body).
  • POST /api/v2/jobs/retry: Retries a failed or interrupted operation (accepts job_id in JSON body).
  • POST /api/v2/jobs/resume: Resumes an interrupted operation (accepts job_id in JSON body).
  • GET /api/events: SSE stream of real-time operation state and progress updates.
  • GET /api/jobs: Backwards-compatible legacy route returning combined live and finished jobs.