Skip to content

Warn the logs, not just the web UI, about the deprecated Docker Hub image - #846

Merged
ajslater merged 3 commits into
developfrom
dockerhub-deprecated-log-warning
Sep 12, 2026
Merged

ajslater merged 3 commits into
developfrom
dockerhub-deprecated-log-warning

Conversation

@ajslater

Copy link
Copy Markdown
Owner

Why

The deprecated Docker Hub image already tells admins to switch registries, but only in the web UI: a snackbar and a settings-footer link. An admin who runs Codex headless, or who simply never opens the settings drawer, had no way to learn that their image had stopped being the real one. That is most of the audience this whole effort is aimed at, since the stranded installs are the ones nobody is actively tending. So say it where they are actually looking.

What changed

The warning text lives in one place, log_docker_hub_deprecation in codex/util.py, beside is_docker(). It returns immediately unless the deprecated image's flag is set, so ghcr.io and native installs emit nothing at all. The message names the registry to move to and links to the migration docs, because a log line nobody can act on is worse than no log line.

Two call sites, no new janitor job:

  • Startup, in codex_init(), alongside the existing lines about the URL path prefix and remote-user auth.
  • Once a day, bundled into the janitor's existing version check, which the nightly task already forces.

The one placement decision worth reviewing

The daily call sits inside the PyPI fetch gate, not at the top of update_latest_version.

While the version cache is empty, every hit on /api/v4/version queues one of these tasks. A call at the top of the method would therefore turn a daily nag into a log flood during a cold cache or a PyPI outage, which is exactly the failure the _FetchGate lock was added to prevent for outbound requests. Inside the lock, only the task that actually wins the fetch logs. In steady state the sole caller is the nightly forced run, so it is once a day. A test covers the flood case directly by holding the lock and asserting silence.

The cost of this choice: if PyPI is unreachable, the failure backoff bounds the nag to once per ten minutes rather than once a day, and a fetch that never succeeds still logs. Both seemed better than the flood.

Verification

  • Booted the server twice against a throwaway config directory. With the flag set the warning appears at WARNING level immediately before "Running Codex v2.3.1"; without it, nothing is logged anywhere.
  • Six new tests in tests/test_latest_version_fetch.py cover the message contents, silence on every other install, the cold-cache flood guard, and the fresh-cache skip.
  • Full suite green: 1121 backend tests, 500 frontend. make fix, make lint and make ty clean.

Notes for the reviewer

  • codex/util.py now imports codex.settings, so it is no longer a leaf module. Nothing under codex/settings/ imports codex.util, so there is no cycle, and the settings module is fully loaded before any app imports util. Both live boots and the full suite confirm it. codex/views/version.py already pairs these same two imports.
  • This branch also carries your own update deps commit (940ce1380), which was in the working tree when the branch was cut. uv lock --check passes, so dependency resolution is sound.
  • Two pre-existing pyright warnings surface in make lint, both unnecessary reportUnusedFunction ignores, in tests/conftest.py:33 and tests/test_onlinetag_snapshot.py:58. Neither is in a file this branch touches.

🤖 Generated with Claude Code

ajslater and others added 3 commits September 12, 2026 15:32
…mage

An admin who runs codex headless and never opens the settings drawer had
no way to learn that their image had stopped being the real one. Say it
where they are actually looking.

The warning names the registry to move to and links to the migration
docs, and only the Docker Hub image ever emits it. It fires at startup
beside the other lines about how this install is configured, and once a
day from the janitor's version check rather than from a new job of its
own. That call sits inside the fetch gate: while the version cache is
empty every API hit queues a check, and only the one that wins the lock
should nag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ajslater
ajslater merged commit 14df7e8 into develop Sep 12, 2026
4 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