fix(setup): deliver first-time setup token reliably to docker logs (#858) - #1168
Merged
Conversation
|
Resolve by running |
The first-time setup token was block-buffered by CPython and never reached `docker logs`, deadlocking fresh installs — the only documented path through the setup token gate. docker/backend/Dockerfile had drifted and lost `ENV PYTHONUNBUFFERED=1` (scheduler still set it), so lifespan print() output sat in the ~8KB Docker pipe buffer. Two-layer fix: - Restore `ENV PYTHONUNBUFFERED=1` in docker/backend/Dockerfile (parity with docker/scheduler/Dockerfile) — catches every print(). - Emit the setup token via logger.warning (the StreamHandler flushes per record, so it survives future Dockerfile drift and flows through Vector), relocated to immediately after setup_logging() so a later startup hang can't suppress it. Convert the remaining lifespan print() calls to structured logger calls; setup_opentelemetry()'s import-time print stays print(flush=True) since it runs before setup_logging(). Adds unit/test_858_dockerfile_unbuffered.py: backend↔scheduler Dockerfile parity guard + AST checks that the token is emitted via logger after setup_logging() and that the lifespan has no print() calls. Closes #858 Co-Authored-By: Claude <noreply@anthropic.com>
Discovered while running the full suite for #858 — unrelated to the setup-token fix itself. - test_agent_analytics / test_schedule_analytics: also restore `db.connection` and the `db` package's child-module attributes between tests. `import db.X` rebinds `db.X` on the package, and later files binding via `import db.X as Y` resolve through the package attribute, not sys.modules — restoring only sys.modules left them with a different module object than `from db.X import ...`, so attribute patches landed on the wrong module (the test_agent_soft_delete DB_PATH mismatch under randomized ordering). - Refresh config/model stubs that predated newer features: VOIP_MAX_CALL_DURATION (#1091), GEMINI_TEXT_MODEL (#1138), and AgentDefaultAccessPolicyUpdate + agent_default_require_email (#1129). Co-Authored-By: Claude <noreply@anthropic.com>
AndriiPasternak31
force-pushed
the
AndriiPasternak31/issue-858
branch
from
June 12, 2026 21:42
79c6681 to
de0b329
Compare
AndriiPasternak31
requested review from
dolho,
pavshulin and
vybe
and removed request for
dolho,
pavshulin and
vybe
June 13, 2026 23:54
vybe
approved these changes
Jun 15, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
✅ Validated & approved (/validate-pr)
P1 devex bug fix (Closes #858). 8 files, +358/-101.
Fix review (two layers, both sound):
docker/backend/Dockerfile: restoresENV PYTHONUNBUFFERED=1to parity with the scheduler image — root cause of the block-bufferedprint()swallowing the setup token. Clearly justified.main.py: setup token now emitted vialogger.warning(StreamHandler flushes per record → immune to future Dockerfile drift, flows through Vector) and relocated to immediately aftersetup_logging(), before event-bus/audit startup that could hang and suppress it. The ~76 other lifespanprint()→ structuredlogger.setup_opentelemetry()import-time print correctly kept asprint(flush=True)(runs beforesetup_logging()).
Tests: test_858_dockerfile_unbuffered.py (Dockerfile parity guard + AST checks: token via logger after setup_logging/before event_bus.start, no print() in lifespan). Feature flow first-time-setup.md + index + tests/registry.json updated.
- Base →
dev✓ · conventional commits ✓ · CI all green - Security scan clean; no new env vars; Dockerfile change justified
- (
main.pyflagged by the new-module COPY check is a false positive — it's the existing app entrypoint, not a new module.)
Notes (non-blocking):
- Closing #858 is correct — the reported block-buffering bug is fixed. The residual prod
--workers 2per-worker-token flakiness is a genuinely distinct bug, correctly tracked as new issue #1165 (not conflated). - The 2nd commit's analytics-test changes (
test_agent_analytics/test_schedule_analyticsdb-package-attr restoration) are unrelated test-isolation hygiene surfaced by running the full suite — test-only, well-commented, transparent. Acceptable. - Duplicate alert: #1180 (→
main) targets the same #858 with the wrong base branch — close it in favor of this PR.
4 tasks
This was referenced Jun 16, 2026
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.
Problem
The first-time setup token was silently lost from
docker logs, deadlocking fresh installs — it's the only documented path through therouters/setup.pytoken gate.docker/backend/Dockerfilehad drifted and lostENV PYTHONUNBUFFERED=1(whichdocker/scheduler/Dockerfilestill sets), so CPython block-buffered the lifespan's stdout into the ~8KB Docker log pipe and the printed token never surfaced.Closes #858 (P1, type-bug, theme-devex).
Fix (two layers)
ENV PYTHONUNBUFFERED=1indocker/backend/Dockerfile(parity with the scheduler image) — catches everyprint().logger.warninginstead ofprint(). The loggingStreamHandlerflushes after every record, so the token is delivered regardless of buffering, survives future Dockerfile drift, and now flows through structured JSON logging / Vector. The token block is relocated to immediately aftersetup_logging(), before the event-bus/audit-write startup that could otherwise hang and suppress it. The remaining ~76 lifespanprint()calls are converted to structuredloggercalls;setup_opentelemetry()'s import-time print staysprint(flush=True)(it runs beforesetup_logging()).Tests
tests/unit/test_858_dockerfile_unbuffered.py(4 tests): backend↔scheduler Dockerfile parity guard (ENV PYTHONUNBUFFERED=1parsed as a real ENV instruction) + AST checks on themain.pylifespan (token emitted vialogger.warningaftersetup_logging()and beforeevent_bus.start(); noprint()anywhere in the lifespan).test:) is unrelated test-suite hygiene surfaced while running the full suite for bug: First-time setup token silently lost — print() block-buffered, breaks fresh installs #858:dbpackage-attribute restoration in the analytics tests (fixes atest_agent_soft_deleteDB_PATH mismatch under randomized ordering) and refreshed config stubs that predated fix(voice/voip): announce before slow tool calls + honor 10-min phone call cap #1091/fix: replace retired gemini-2.0-flash with env-configurable model (#1130) #1138/Make 'require verified email' access policy a fleet-wide default config (secure-by-default) #1129.Verified locally:
test_858(4 passed) + the 5 touched unit files (77 passed).Notes / follow-up
--workers 2, so the per-process token is still ~50% flaky until the multi-worker token is unified. Documented in the feature flow.🤖 Generated with Claude Code