Repository navigation
Conversation
The readiness banner is the signal developers wait on before opening the browser, so it has to account for the component that serves it. The API server is the slowest of the four to come up, and until it binds its port the banner points users at a connection they cannot make. The port check existed from the command's introduction and was lost as collateral damage when the old webserver was removed: apache#46942 deleted the attribute holding the port but left the call reading it, and the follow-up hot-fix apache#47145 resolved the resulting AttributeError by deleting the call rather than repointing it at the renamed config key. The probe and its tests have been unreachable since. Resolving the port once during startup, as the pre-apache#47145 code did, also keeps a raising config lookup out of the poll loop, whose only handler is KeyboardInterrupt and whose component shutdown sits after the try block.
Eason09053360
requested review from
bugraoz93,
dheerajturaga,
henry3260 and
potiuk
as code owners
September 12, 2026 09:20
potiuk
approved these changes
Sep 12, 2026
xvega
pushed a commit
to xvega/airflow
that referenced
this pull request
Sep 13, 2026
…up (apache#73025) The readiness banner is the signal developers wait on before opening the browser, so it has to account for the component that serves it. The API server is the slowest of the four to come up, and until it binds its port the banner points users at a connection they cannot make. The port check existed from the command's introduction and was lost as collateral damage when the old webserver was removed: apache#46942 deleted the attribute holding the port but left the call reading it, and the follow-up hot-fix apache#47145 resolved the resulting AttributeError by deleting the call rather than repointing it at the renamed config key. The probe and its tests have been unreachable since. Resolving the port once during startup, as the pre-apache#47145 code did, also keeps a raising config lookup out of the poll loop, whose only handler is KeyboardInterrupt and whose component shutdown sits after the try block. Co-authored-by: Eason09053360 <185830721+Eason09053360@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
airflow standaloneprints "Airflow is ready" without checking the component that serves the UI.is_ready()only looks at the scheduler, Dag processor and triggerer heartbeats, so the banner fires while the API server is still importing the app and binding its port. Developers follow the banner to the browser and get a connection refused.The check is not new.
is_ready()gated on the port from the command's introduction in #15826, and #20505 later fixed it to read the configured port instead of a hard-coded 8080. #46942 ("Remove old UI and webserver") then deleted the attribute holding the port but left the call that read it, sois_ready()raisedAttributeError; the follow-up hot-fix #47145 resolved that by deleting the call rather than repointing it at the renamed[api] port.port_openand its two tests have been unreachable since.What
airflow-core/src/airflow/cli/commands/standalone_command.py:is_ready()gates onport_openagain, first in the chain so the three heartbeat queries are skipped while the API server is still coming up.run()resolves[api] portonce at startup, matching the shape the pre-Fix standalone command by removing the usage of webserver_port property #47145 code used. Keeping the lookup out of the poll loop matters:conf.getintraises on a malformed value, the loop only handlesKeyboardInterrupt, and thecommand.stop()/command.join()shutdown sits after thetryrather than in afinally, so an escape would leave all four components running as orphans. With the lookup inrun(), a bad[api] portfails before anything is spawned.is_ready()claimed to be time-based, which it has not been since Add Airflow Standalone command #15826, andport_openstill referred to the webserver.airflow-core/tests/unit/cli/commands/test_standalone_command.py: theis_readytests are consolidated into one parametrized test covering port-open, a component not heartbeating, and port-closed. It assertsport_openis called with the resolved port, and the port-closed case passes an empty iterator so the short-circuit is pinned. A new test covers the config resolution inrun(). Reverting the source change fails all four.Deliberately not in this PR
port_openis restored as it was, so its pre-existing limitations come back with it and are worth a maintainer's call:127.0.0.1over IPv4 and ignores[api] host. The default0.0.0.0is fine, but a user who binds a specific non-loopback address would never see the banner.[api] workers > 1orserver_type = gunicornthe parent binds before workers import the app, so the accept precedes app readiness.All three point at the same deeper fix: polling
GET /api/v2/monitor/health, which is already the idiom in the Helm chart, breeze, anddev/verify_*_rc.sh, and which would subsume the heartbeat checks too. That is a larger change than restoring a lost gate, so I have kept it out.Separately,
ready_timelatches and is never re-evaluated, so the banner still makes a 3-second-stale claim, and standalone never notices a component dying. Both are pre-existing and unchanged here..github/workflows/basic-tests.ymlgreps this banner, so that job now genuinely depends on the API server binding.How to test
Run
airflow standaloneand confirm the banner appears only oncehttp://localhost:8080actually answers.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines