fix(client): flush the device code, or nobody ever sees it

Hit for real driving a first sign-in: `granthi-sync link` run through anything
that is not a terminal -- a wrapper, a pipe, `| tee setup.log` -- printed
NOTHING, while the code it was holding silently expired five minutes later.
Python buffers stdout when stdout is not a tty, and the two prints carrying
the verification URL and the user code did not flush.

They flush now. One regression test asserts both are printed with flush=True,
because this fails silently and only under redirection, which is exactly the
condition a test would otherwise never reproduce.

204 tests (was 203).
This commit is contained in:
claude
2026-08-23 15:58:34 -04:00
parent 6d62419fc7
commit e77c0077e3
2 changed files with 33 additions and 2 deletions
+7 -2
View File
@@ -599,8 +599,13 @@ def device_flow():
form={"client_id": DEVICE_CLIENT_ID, "scope": DEVICE_SCOPE})
if status != 200:
raise SystemExit(f"device authorization failed (HTTP {status}): {resp}")
print(f"\nTo link this device, open:\n\n {resp.get('verification_uri_complete') or resp.get('verification_uri')}\n")
print(f"and enter code: {resp['user_code']}\n")
# flush=True is not cosmetic. Python buffers stdout when it is not a
# terminal, so `granthi-sync link | tee setup.log`, a wrapper script, or
# anything capturing output shows NOTHING while the code silently expires
# five minutes later. Hit for real on 2026-08-23 driving a first sign-in.
print(f"\nTo link this device, open:\n\n {resp.get('verification_uri_complete') or resp.get('verification_uri')}\n",
flush=True)
print(f"and enter code: {resp['user_code']}\n", flush=True)
interval = int(resp.get("interval", 5))
deadline = time.time() + int(resp.get("expires_in", 300))
while time.time() < deadline: