fix: missing nonlocal on _drain's tracker vars (1.0.0-59)
Some checks are pending
Security scan / pip-audit (push) Waiting to run
Security scan / bandit (push) Waiting to run
Security scan / gitleaks (push) Waiting to run
Security scan / mypy (push) Waiting to run

After the chunk-read refactor, the inner _drain coroutine assigns
to last_db_write_ts and last_pct_sample. Without nonlocal, Python
compiles these as locals of _drain, so any READ before the first
assignment raises UnboundLocalError.

In 1.0.0-55 / -57 the bug was hidden by gather(return_exceptions=
True), which silently swallowed the exception — the drain coroutine
ended immediately, the asyncssh channel buffer filled up, and the
remote badblocks blocked on pipe_write. THAT was the actual cause
of the "parser silently never works" symptom, not anything to do
with the chunk-read or tr-pipe logic itself.

1.0.0-57 dropped the gather (single drain after merging 2>&1), which
made the next deploy surface the bug as an explicit error_text on
the surface_validate stage: "cannot access local variable
'last_db_write_ts' where it is not associated with a value".

Fix: add both vars to the nonlocal declaration. pending_log_chunks
only gets .append/.clear (no reassignment) so it doesn't need
nonlocal.

This is the bug that's been hiding behind all the recent parser
work. Sorry for the round trips.
This commit is contained in:
Brandon Walter 2026-05-13 10:31:35 -07:00
parent 129f233e0a
commit 7e42464016
2 changed files with 2 additions and 2 deletions

View file

@ -556,7 +556,7 @@ async def _stage_surface_validate_ssh(job_id: int, devname: str, drive_id: int)
_push_update() _push_update()
async def _drain(stream, is_stderr: bool): async def _drain(stream, is_stderr: bool):
nonlocal bad_blocks_total, pid_seen nonlocal bad_blocks_total, pid_seen, last_db_write_ts, last_pct_sample
# Line-based drain. The wrapped badblocks command # Line-based drain. The wrapped badblocks command
# pipes through `tr '\b' '\n'` at the shell level # pipes through `tr '\b' '\n'` at the shell level
# so every progress update is a real newline- # so every progress update is a real newline-

View file

@ -86,7 +86,7 @@ class Settings(BaseSettings):
ssh_key: str = "" # PEM private key content (paste full key including headers) ssh_key: str = "" # PEM private key content (paste full key including headers)
# Application version — used by the /api/v1/updates/check endpoint # Application version — used by the /api/v1/updates/check endpoint
app_version: str = "1.0.0-58" app_version: str = "1.0.0-59"
# ---- Authentication (1.0.0-22) ---- # ---- Authentication (1.0.0-22) ----
# session_secret: HMAC key for signing session cookies. Empty = generate # session_secret: HMAC key for signing session cookies. Empty = generate