Summary
On Windows, the lark-cli.cmd shim shipped with @larksuite/cli mangles arguments that contain newlines or >, which breaks the primary agent use-case of im +messages-send --markdown (multi-line, report-style markdown). A multi-line --markdown silently routes the request to the user identity (fails with token_missing even though --as bot was passed), and content with a line-leading > fails with a Windows path error. Both work perfectly when the same command is invoked through node run.js directly, which isolates the bug to the .cmd shim's argument re-passing — the core CLI binary is fine.
Repro
lark-cli 1.0.86, Windows 11, bot identity configured (user identity not logged in).
Case 1 — multi-line --markdown fails via .cmd, works via node run.js:
# via lark-cli.cmd → rc=3, routes to USER identity:
lark-cli.cmd im +messages-send --chat-id oc_xxx --markdown "line one
line two" --as bot
Actual error payload (verbatim):
{
"ok": false,
"identity": "user",
"error": {
"type": "authentication",
"subtype": "token_missing",
"message": "need_user_authorization (user: ou_9aaa070f0debd6a287298322cf962023)",
"hint": "run `lark-cli auth login --scope \"im:message.send_as_user im:message\" --no-wait --json` ...",
"user_open_id": "ou_9aaa070f0debd6a287298322cf962023"
}
}
- Single-line
--markdown via .cmd → rc=0, "identity": "bot", sends fine.
- Multi-line
--markdown via node run.js → rc=0, "identity": "bot", sends fine (so the CLI logic itself handles multi-line correctly).
Case 2 — > inside content fails via .cmd, works via node run.js:
# via lark-cli.cmd → rc=1, WinError "The system cannot find the path specified."
lark-cli.cmd im +messages-send --chat-id oc_xxx \
--content '{"zh_cn":{"content":[[{"tag":"md","text":"# 标题\n\n> 引用行\n\n**正文**"}]]}}' \
--msg-type post --as bot
# via node run.js → rc=0, sends fine.
node node_modules/@larksuite/cli/scripts/run.js im +messages-send --chat-id oc_xxx \
--content '{"zh_cn":{"content":[[{"tag":"md","text":"# 标题\n\n> 引用行\n\n**正文**"}]]}}' \
--msg-type post --as bot
A mid-line > (e.g. l=64.67 > zg=63.30) works fine via .cmd; only a line-leading > (markdown blockquote) triggers the path error.
Why this is worse than a one-shot failure
- AI agents and report push scripts almost always send multi-line markdown (headers, blockquotes, bullet lists). The
.cmd shim is what npm exposes as the default lark-cli entry on Windows (see node_modules/@larksuite/cli/scripts/run.js + lark-cli.cmd), so every script that does subprocess.run(["lark-cli", ...]) or shutil.which("lark-cli") lands on the broken .cmd.
- The failure is silent and misleading: it looks like an auth problem (
need_user_authorization, hint to add im:message.send_as_user scope) when the user is trying to send as a bot that is already fully authorized. This sent us down a false "user auth missing" debugging path before we bisected to the wrapper.
- The blockquote case produces a confusing Windows path error for a pure-text payload, so even the manual
--content workaround is non-obvious.
Root cause
The shim at node_modules/@larksuite/cli/lark-cli.cmd re-passes argv via %* into a second invocation:
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & set PATHEXT=%PATHEXT:;.JS;=;% & "%_prog%" "%dp0%\node_modules\@larksuite\cli\scripts\run.js" %*
cmd.exe's %* re-expansion loses the original argv quoting: embedded newlines break the argument (the CLI then sees a malformed flag set and falls back to its default user identity), and a line-leading > is treated as output redirection (WinError "The system cannot find the path specified"). The Go binary behind run.js has no such problem — proof is the node run.js control above.
Proposed fixes
(a) Stop re-parsing through %*. The .cmd shim should not reconstruct argv from %*. Preferred: keep the shim as a thin launcher that calls run.js with the exact same argv the shell received (e.g. forward args without re-expansion, or ship a small .exe/node launcher that does spawnSync(node, [runjs, ...process.argv.slice(2)])). This single change resolves both cases.
(b) Fall back loudly instead of silently. Independently of (a), when --as bot is passed but the request would use the user identity, error out with a clear "bot identity requested but user path taken" message rather than returning the confusing token_missing auth hint.
(a) alone is sufficient to unblock affected users.
Workaround (for affected users today)
On Windows, bypass the .cmd shim and call the underlying script with node directly:
node "$(npm root -g)/@larksuite/cli/scripts/run.js" im +messages-send \
--chat-id oc_xxx --markdown "$(cat report.md)" --as bot
For the bundled-runtime case (e.g. inside Hermes): node <node-dir>/node_modules/@larksuite/cli/scripts/run.js ....
Environment
@larksuite/cli 1.0.86 (npm), Windows 11 x64
- Invocation path:
lark-cli.cmd shim → scripts/run.js → compiled Go binary (bin/lark-cli.exe)
- Identity: bot configured and authorized; user identity intentionally not logged in
- Feishu Open Platform (chat send via
/open-apis/im/v1/messages)
Summary
On Windows, the
lark-cli.cmdshim shipped with@larksuite/climangles arguments that contain newlines or>, which breaks the primary agent use-case ofim +messages-send --markdown(multi-line, report-style markdown). A multi-line--markdownsilently routes the request to the user identity (fails withtoken_missingeven though--as botwas passed), and content with a line-leading>fails with a Windows path error. Both work perfectly when the same command is invoked throughnode run.jsdirectly, which isolates the bug to the.cmdshim's argument re-passing — the core CLI binary is fine.Repro
lark-cli 1.0.86, Windows 11, bot identity configured (user identity not logged in).
Case 1 — multi-line
--markdownfails via.cmd, works vianode run.js:Actual error payload (verbatim):
{ "ok": false, "identity": "user", "error": { "type": "authentication", "subtype": "token_missing", "message": "need_user_authorization (user: ou_9aaa070f0debd6a287298322cf962023)", "hint": "run `lark-cli auth login --scope \"im:message.send_as_user im:message\" --no-wait --json` ...", "user_open_id": "ou_9aaa070f0debd6a287298322cf962023" } }--markdownvia.cmd→ rc=0,"identity": "bot", sends fine.--markdownvianode run.js→ rc=0,"identity": "bot", sends fine (so the CLI logic itself handles multi-line correctly).Case 2 —
>inside content fails via.cmd, works vianode run.js:A mid-line
>(e.g.l=64.67 > zg=63.30) works fine via.cmd; only a line-leading>(markdown blockquote) triggers the path error.Why this is worse than a one-shot failure
.cmdshim is whatnpmexposes as the defaultlark-clientry on Windows (seenode_modules/@larksuite/cli/scripts/run.js+lark-cli.cmd), so every script that doessubprocess.run(["lark-cli", ...])orshutil.which("lark-cli")lands on the broken.cmd.need_user_authorization, hint to addim:message.send_as_userscope) when the user is trying to send as a bot that is already fully authorized. This sent us down a false "user auth missing" debugging path before we bisected to the wrapper.--contentworkaround is non-obvious.Root cause
The shim at
node_modules/@larksuite/cli/lark-cli.cmdre-passes argv via%*into a second invocation:cmd.exe's%*re-expansion loses the original argv quoting: embedded newlines break the argument (the CLI then sees a malformed flag set and falls back to its default user identity), and a line-leading>is treated as output redirection (WinError "The system cannot find the path specified"). The Go binary behindrun.jshas no such problem — proof is thenode run.jscontrol above.Proposed fixes
(a) Stop re-parsing through
%*. The.cmdshim should not reconstruct argv from%*. Preferred: keep the shim as a thin launcher that callsrun.jswith the exact same argv the shell received (e.g. forward args without re-expansion, or ship a small.exe/node launcher that doesspawnSync(node, [runjs, ...process.argv.slice(2)])). This single change resolves both cases.(b) Fall back loudly instead of silently. Independently of (a), when
--as botis passed but the request would use the user identity, error out with a clear "bot identity requested but user path taken" message rather than returning the confusingtoken_missingauth hint.(a) alone is sufficient to unblock affected users.
Workaround (for affected users today)
On Windows, bypass the
.cmdshim and call the underlying script with node directly:For the bundled-runtime case (e.g. inside Hermes):
node <node-dir>/node_modules/@larksuite/cli/scripts/run.js ....Environment
@larksuite/cli1.0.86 (npm), Windows 11 x64lark-cli.cmdshim →scripts/run.js→ compiled Go binary (bin/lark-cli.exe)/open-apis/im/v1/messages)