Skip to content

Temporal Worker: WORKERROLE deployment-safety default

Date: 2026-08-13 Type: Behavior change (operational safety) Severity: Medium — no API change; changes what worker.py does when run outside a deployment Shipped in: staging → main promotion #2980 (2026-08-13) Change PR: #2975

apps/temporal-worker/worker.py now reads a WORKER_ROLE environment variable that selects what the process does:

WORKER_ROLE Serves Creates schedules
pdf-only (default) pdf-extraction only No
full pdf-extraction + email-processing Yes (~10)

The default is pdf-only. Only apps/temporal-worker/docker-entrypoint.sh sets full, and that entrypoint is the sole launcher the deployment uses (Dockerfile CMD; the systemd unit in infrastructure-gcp/ansible/playbooks/temporal-workers.yml runs the image with no command override). So a deployed worker is byte-for-byte unchanged, and a bare python worker.py on a laptop no longer registers schedules or serves the email queue.

An unrecognised WORKER_ROLE value raises on startup rather than falling back — a typo in a deployed .env crash-loops visibly instead of silently parking a deployed worker in pdf-only with its schedules and email queue offline.

Running worker.py on a laptop pointed at staging registered all ~10 schedules against the staging database and the email-pull-dispatcher schedule actually fired. It was harmless only by luck: REDIS_URL was unset, so no connection was ever made. Two live hazards it would otherwise have hit:

  • the email-pull dispatcher polls real customer mailboxes every 3 minutes, using whatever credentials are in the environment;
  • the cleanup schedules delete from the database and from the R2 bucket prod and staging share — each environment’s cleanup deletes the other environment’s uploads.

worker.py is built for a deployed environment; nothing in it previously knew that. WORKER_ROLE makes “this is a deployment” an explicit fact the container entrypoint asserts, rather than a heuristic the worker has to guess.

  • To extract a document locally, run the default — no flag, nothing to remember:

    Terminal window
    cd apps/temporal-worker
    TEMPORAL_ADDRESS=localhost:17233 uv run python worker.py
  • Do not set WORKER_ROLE=full on a laptop to “run the real worker” without understanding that it turns on the mailbox poller and the shared-bucket cleanup jobs above.

  • docker compose up temporal-worker runs the same image, so it still gets full and reconciles schedules against the local Temporal — unchanged. If your root .env points at staging, that stack still polls real mailboxes.

See apps/temporal-worker/README.md (WORKER_ROLE section) for the full run guide.

Both environments were confirmed live on the new code, running in full mode (the deployed behavior is unchanged):

Environment VM Container recreated Unit image ref Startup log
staging erp-staging-temporal 2026-08-13 12:22:40 temporal-worker:latest-staging Starting Temporal workers (line 602)
production erp-prod-temporal 2026-08-13 12:44:10 temporal-worker:latest-main Starting Temporal workers (line 602)

The Starting Temporal workers line only executes on the full path; the pdf-only path logs a WORKER_ROLE=pdf-only warning and returns before it. That line moving to 602 (from 562 on the prior code) is also the fingerprint that the running process contains the new WORKER_ROLE block.

The behavior gate is opt-out via the entrypoint. To force the old always-full behavior without a code change, set WORKER_ROLE=full in the worker’s environment. To revert the code, revert #2975 and redeploy; the deployed path was unchanged, so a revert is low-risk.

The 2026-08-13 staging → main promotion carried 13 commits. The rest are routine fixes, documented by their own PRs and changesets:

  • fix(webapp): server-side duplicate-order guard + WhereFour -OM seed path (#2959)
  • fix(dagster): resolve WhereFour tier prices against the live catalog (#2971)
  • fix(workbench): sync marimo’s direct filesystem writes to git (#2979)
  • chatwoot state-locking cherry-pick (#2956)
  • fix(infra-gcp): R2 backend native state locking, chatwoot-web Cloud Run start, dead Cloud Build trigger cleanup, staging terraform-drift reconcile; fix(ci): Terraform runner bump (#2953)
  • chore: version packages (#2961, #2957)