Skip to content

[ENG-3568] Stop login hanging when an assistant runs it - #155

Merged
mariojgt merged 3 commits into
mainfrom
mariot/eng-3568-login-agent-friendly
Aug 20, 2026
Merged

[ENG-3568] Stop login hanging when an assistant runs it#155
mariojgt merged 3 commits into
mainfrom
mariot/eng-3568-login-agent-friendly

Conversation

@mariojgt

@mariojgt mariojgt commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Reported from Lovable: login appears to hang. It does — and the cause is structural, not a quirk of that tool.

🥞 Merge after #153 and #154 (both merged).

Why

An assistant runs a command, waits for it to exit, then reads stdout. login printed the link at second zero and blocked for ten minutes. So the agent saw nothing until the process exited, by which point the code had already expired. Every assistant hits this; Lovable just reported it.

The fix — the second step is the same command

1.  npx @patchstack/connect login   → prints the link, exits (~1s)
2.  user approves in the browser
3.  npx @patchstack/connect login   → resumes the same request and finishes

Re-running resumes the pending request rather than issuing a new one:

  • Approved → redeems, writes the credential, done
  • Not yet → reports the code and the seconds remaining, exits
  • Expired → starts a fresh request

The CLI picks the shape by whether anyone is watching:

Caller Behaviour
Interactive terminal Unchanged — print the link, then wait
Output captured (assistant, pipe) Print the link and exit; re-run to finish

A person at a shell still gets the one-command flow they had. --wait remains as the explicit blocking variant.

Why not leave step 3 as --wait

Splitting start from wait fixed the hang but opened a gap: nothing writes the credential unless someone comes back and runs the second command. An assistant that forgets leaves the user having approved for nothing.

Making step 3 the same command as step 1 removes the flag an assistant has to remember, and re-running can no longer invalidate the link the user is looking at — which the previous docs had to warn against. The obvious action is now the correct one.

Verified against production

RUN 1:  Approve at: …/device?code=ZBYN-7ELY   (valid for another 308s)
RUN 2:  Still waiting for approval of code ZBYN-7ELY
        (valid for another 307s)

Same code, countdown decrementing — resuming, not restarting.

An abandoned flow is harmless

RunPulseDeviceFlow::approve() only marks the request server-side; the rotation happens on redeem. So if step 3 never runs, nothing changed, the request expires in ten minutes, and the site keeps working on its existing credential. No orphaned state to clean up.

The pending request

Held in os.tmpdir(), keyed by a hash of the site UUID, mode 0600. Not the project directory — the device code is a ten-minute secret and nothing that short-lived belongs somewhere it could be committed. The device code is never printed; only the user code and link are.

Closes ENG-3568

🤖 Generated with Claude Code

Reported from Lovable: the command appears to hang. It does, from the
caller's side. An assistant runs a command, waits for it to exit, and only
then reads stdout — so a command that prints a link at second zero and then
blocks for ten minutes shows nothing until the code has already expired.

Splits the flow. `startLogin` returns as soon as the link exists;
`waitForApproval` polls. The CLI picks by whether anyone is watching:

- interactive terminal — unchanged: print the link, then wait
- output captured — print the link and exit, then `login --wait` resumes

The pending request is handed between the two invocations through a file in
the temp directory, keyed by site. Not the project directory: the device
code is a secret with a ten-minute life, and nothing that short-lived
belongs somewhere it could be committed. It is never printed either, so it
cannot leak through an assistant's captured output.

Verified against production: piped, the command now exits in about a second
instead of blocking.

AGENT-INSTALL states the two-step sequence and the three ways an assistant
would otherwise get it wrong — wrapping step one in a timeout, re-running it
to retry (which invalidates the link the user is looking at), and running
--wait before the user has approved.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Aug 20, 2026

Copy link
Copy Markdown

Clear splitting of interactive and automated login flows solves hanging issue.

🎯 Quality: 100% Elite · 📦 Size: Large — consider splitting if possible

📈 This month: Your 59th PR — above team average · Averaging Excellent

See how your team is trending →

mariojgt and others added 2 commits August 20, 2026 09:31
Splitting start from wait left a gap: nothing writes the credential unless
someone comes back and runs the second command. An assistant that forgets
leaves the user having approved for nothing.

Re-running `login` now resumes the pending request rather than issuing a new
one. If the owner has approved, it redeems and finishes; if not, it reports
the code and the time remaining and exits. So the second step is the same
command as the first, which is one less thing for an assistant to get right,
and re-running can no longer invalidate the link the user is looking at —
which the previous docs had to warn against.

An abandoned flow stays harmless. Approving only marks the request server
side; the rotation happens on redeem. Nothing changes until the CLI comes
back, so a forgotten flow expires quietly and the site keeps working.

Verified against production: a second run reports the same code with the
countdown decremented, rather than starting a fresh one.

--wait remains as the blocking variant for anyone who wants it.

Co-Authored-By: Claude <noreply@anthropic.com>
@mariojgt

Copy link
Copy Markdown
Contributor Author

/review

@mariojgt
mariojgt merged commit 8d4c2d7 into main Aug 20, 2026
6 checks passed
@mariojgt
mariojgt deleted the mariot/eng-3568-login-agent-friendly branch August 20, 2026 09:15
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.

2 participants