Skip to content

fix(compose): the worker's store init now runs on every up, after the core - #344

Merged
Opselon merged 4 commits into
masterfrom
fix/worker-store-init-order
Nov 6, 2026
Merged

Opselon merged 4 commits into
masterfrom
fix/worker-store-init-order

Conversation

@Opselon

@Opselon Opselon commented Nov 6, 2026

Copy link
Copy Markdown
Owner

What broke

The v2.5.0 clean-install gate (release run 42301129426) caught a real fresh-install bug:

atf-worker | worker: probe write: attempt to write a readonly database

The worker container crash-looped on startup, its /ops endpoint (probed by the API's health payload) never came up, the stack reported degraded, and the release gate failed. A stock docker compose up install shipped with a dead worker — this is exactly what the clean-install gate exists to catch.

Root cause — a profile/ordering mismatch

  • The worker container is not profile-gated: it always runs to report health (only execution is gated by ATF_WORKER_ENABLED).
  • But worker-store-init — the one-shot that chgrp 101 + g+ws the shared atf.db for the worker's non-root user — was behind profiles: ["worker"], so a stock up never ran it.
  • The worker (uid 100) then probed a root-owned atf.db, fail-closed on purpose (per fix(worker): refuse to start on an unwritable store; mount the shared db at a worker-owned path #285's "refuse to start on an unwritable store"), and restart-looped.

The fix — two ordering holes, not one

  1. The init lost its profile: it runs on every compose up now.
  2. The init waits for the core to be healthy, and the worker waits for the init to complete. On a fresh volume atf.db does not exist until the core creates it — an init that ran earlier would chgrp an empty directory and leave the newly created file root-owned, reintroducing the same crash loop on first boot.

Idempotency is unchanged (re-runs after a core restart stay safe); postgres keeps its required: false profile gate.

Verification

  • docker compose config clean.
  • The exact clean-install gate run locally (Docker 29.7.2): .env.example + ATF_DISABLE_BOT=1, compose up -d --build, and the same health-poll assertion — to be confirmed green before merge (result posted in this PR).

… core

The v2.5.0 clean-install gate caught a real fresh-install bug: the
worker container crash-looped with "probe write: attempt to write a
readonly database" and the stack never reported healthy.

Root cause: the worker container is not profile-gated — it always runs
to report health (only execution is gated by ATF_WORKER_ENABLED) — but
the worker-store-init that grants the worker's group write access to
atf.db was behind profiles: ["worker"], so a stock `docker compose up`
never ran it. The worker (uid 100) then probed a root-owned atf.db,
fail-closed on purpose (#285), restart-looped, and its /ops endpoint —
which the API's health payload probes — never came up: status degraded,
gate failed.

Two ordering holes are closed, not one:

1. The init lost its profile: it runs on every `compose up` now.
2. The init waits for the core to be healthy, and the worker waits for
   the init to complete. On a fresh volume atf.db does not exist until
   the core creates it — an init that ran earlier would chgrp an empty
   directory and leave the newly created file root-owned, reintroducing
   the same crash loop on the first boot.

Idempotency is unchanged: chgrp/chmod on an already-prepared store is a
no-op, so re-runs after a core restart stay safe, and the postgres
dependency keeps its `required: false` profile gate.
@chatgpt-codex-connector

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ❌ Failed 2026-11-06T22:29:24.047089Z
We couldn’t complete this review. Comment @codex review to try again.
be0f7db PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Capsizer added 3 commits November 7, 2026 02:00
The real gate only runs on v* tags, which is how the v2.5.0 clean
install failed after the tag was cut. This is release.yml's
clean-install job, copied verbatim, triggered by PRs that touch the
compose stack. Deleted once the fix has shipped.
…ooping

The second layer the clean-install gate exposed after the store-init
fix: with ATF_CORE_RPC_TOKEN empty the worker exited at startup with
"internal RPC token is empty: refusing an unauthenticated client" —
even when ATF_WORKER_ENABLED=0. The container then restart-looped, its
/ops endpoint (probed by the API's health payload) never served, the
stack reported degraded, and the release gate failed again.

The compose contract always said refusal is for an ENABLED worker:
"Empty + enabled = refuse to start, on purpose. Until then the Python
Core keeps its queue loop and this worker only reports health." The
code deviated by constructing the gateway client unconditionally and
treating its refusal as fatal regardless of the switch.

The disabled path now boots with the health-only telegraph.Gateway
(internal/telegraph/unconfigured): every operation fails closed with
TRANSPORT_UNCONFIGURED — non-transient by construction, the refusal
carries the startup reason verbatim and names the operation that was
never attempted — so a latent caller gets an honest failure instead of
a nil-pointer panic. An ENABLED worker with an empty token still
refuses to start: fail closed, on purpose.
Its purpose is served: the gate ran on this PR, caught the regressions
before any tag, and is now green (3m43s). The permanent home for this
check is release.yml's clean-install job on every v* tag; keeping the
copy would run a full second compose stack on every master PR.
@Opselon
Opselon merged commit 25e2f58 into master Nov 6, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant