Summary
lark-cli's device-flow authorization guidance for AI agents steers the agent into running the blocking auth login --device-code poll inside the same agent turn that produced the verification URL. On agent harnesses that deliver only the agent's final turn message (i.e. they don't stream intermediate output), the verification URL is never delivered — it stays buffered in a turn that never completes while the poll blocks. The user sees nothing and the agent appears to hang silently, usually until the device code expires, after which it often retries, producing an effectively silent infinite loop.
Environment
lark-cli (@larksuite/cli) 1.0.30
- Harness: a headless
claude agent (Claude Code -p / print mode) embedded in a chat gateway that posts the agent's reply to chat only when the turn completes; intermediate assistant messages are not streamed.
What happens
lark-cli docs +fetch … → Error: need_user_authorization (user: ou_…)
lark-cli auth login --domain docs --no-wait --json → returns device_code, verification_url, and a hint.
- The
hint says, verbatim: "Show verification_url to the user … Then immediately execute: lark-cli auth login --device-code <code> (blocks until authorized or timeout). Do not instruct the user to run this command themselves." — and auth login --help says "this command blocks until the user completes authorization in the browser. Run it in the background…".
- Following that guidance, the agent emits the URL as an assistant message and then runs the blocking
--device-code poll within the same turn.
- The turn never completes (the poll blocks for the device code's full lifetime). Because the harness delivers only the final turn message, the URL the agent emitted is never sent to the user.
- The user sees nothing; the device code expires; the agent commonly retries from step 2.
Observed in practice: the agent produced a correct "please authorize: <url>" message six times over ~30 minutes — the user received none of them, because the turn never ended.
Why the current guidance is fragile
It implicitly assumes the harness surfaces output produced mid-turn (streaming). Interactive Claude Code in a terminal does. But many chat-gateway / async agent integrations deliver only the agent's final turn output — for those, "show the URL, then block in-turn" guarantees the URL is never seen.
Suggested fix
Change the device-flow guidance — the auth login --help "For AI agents" text, the hint field in auth login --no-wait --json output, and the bundled lark-* agent skills — to recommend a yield pattern instead of a block pattern:
On need_user_authorization: run auth login --no-wait to obtain the verification URL, present the URL to the user as the final message of the turn, then end the turn / return control. Do not block on --device-code within the same turn. Complete authorization by polling --device-code in a separate, later step (e.g. after the user replies that they have authorized).
The --no-wait / --device-code split already supports this cleanly — only the guidance steers agents the wrong way. At minimum, the hint should drop "immediately execute … (blocks …)" and make clear that, for harnesses without mid-turn streaming, the agent should end its turn after presenting the URL.
(Likely distinct from #747, which concerns the poll's timeout duration rather than the turn-blocking guidance.)
Summary
lark-cli's device-flow authorization guidance for AI agents steers the agent into running the blockingauth login --device-codepoll inside the same agent turn that produced the verification URL. On agent harnesses that deliver only the agent's final turn message (i.e. they don't stream intermediate output), the verification URL is never delivered — it stays buffered in a turn that never completes while the poll blocks. The user sees nothing and the agent appears to hang silently, usually until the device code expires, after which it often retries, producing an effectively silent infinite loop.Environment
lark-cli(@larksuite/cli)1.0.30claudeagent (Claude Code-p/ print mode) embedded in a chat gateway that posts the agent's reply to chat only when the turn completes; intermediate assistant messages are not streamed.What happens
lark-cli docs +fetch …→Error: need_user_authorization (user: ou_…)lark-cli auth login --domain docs --no-wait --json→ returnsdevice_code,verification_url, and ahint.hintsays, verbatim: "Show verification_url to the user … Then immediately execute: lark-cli auth login --device-code <code> (blocks until authorized or timeout). Do not instruct the user to run this command themselves." — andauth login --helpsays "this command blocks until the user completes authorization in the browser. Run it in the background…".--device-codepoll within the same turn.Observed in practice: the agent produced a correct "please authorize: <url>" message six times over ~30 minutes — the user received none of them, because the turn never ended.
Why the current guidance is fragile
It implicitly assumes the harness surfaces output produced mid-turn (streaming). Interactive Claude Code in a terminal does. But many chat-gateway / async agent integrations deliver only the agent's final turn output — for those, "show the URL, then block in-turn" guarantees the URL is never seen.
Suggested fix
Change the device-flow guidance — the
auth login --help"For AI agents" text, thehintfield inauth login --no-wait --jsonoutput, and the bundledlark-*agent skills — to recommend a yield pattern instead of a block pattern:The
--no-wait/--device-codesplit already supports this cleanly — only the guidance steers agents the wrong way. At minimum, thehintshould drop "immediately execute … (blocks …)" and make clear that, for harnesses without mid-turn streaming, the agent should end its turn after presenting the URL.(Likely distinct from #747, which concerns the poll's timeout duration rather than the turn-blocking guidance.)