Claude Code × 阿里无影企业版 · 控制手册

把一台 Windows
交给终端里的 Claude

机器在成都,人在杭州,中间只有一条异步远程命令 API。这篇讲清楚:这条链路怎么搭、每一段能干什么、以及你以为坏掉的地方,多半是自己参数按错了

// 面向工程与运维 · 全部结论标注验证方式 · 数据截至 2026-07-25

ALIYUN ECD 2020-09-30 RUNCOMMAND / POWERSHELL SESSION 0 → SESSION 1 PYAUTOGUI + QWEN3-VL 2 台 · 6C12G · 成都 CLAUDE CODE / OPUS
SCROLL / 向下滚动
01
Why Remote-Control a Desktop At All

因为有些活,只能在真机上干

这不是「云上跑个脚本」的故事。企业微信和抖音都没有可用的对外发消息接口——前者的客服 API 卡在备案,后者对机房 IP 风控极严。于是唯一能把消息真发出去的办法,是在一台登录着真实账号的 Windows 上,模拟人去点鼠标、敲键盘

Constraint

需要 GUI,就需要一台常开的 Windows;需要常开的 Windows,就不能靠人天天远程桌面连上去伺候。要么雇一个人盯着,要么让 Claude 去盯着——这条链路存在的全部理由。

The Job◉ GUI

它在干什么

企业微信 · 抖音 PC 客户端

一个 Python 常驻循环:截屏 → 视觉模型读出新消息 → 调后端大脑生成回复 → 定位输入框打字 → 点发送。全程没有一次 API 调用碰到企微或抖音的官方接口。

wecom_reply_loop.py · 88 KB · 常驻
The Gap◈ 个人版

为什么必须企业版

EDSP → EDS Enterprise

个人版无影只能人坐在客户端前面操作。企业版多出来的关键能力是一套面向管理员的 OpenAPI——其中就包含云助手的远程命令。没有它,一切自动化免谈。

eds.enterprise_office.6c12g
The Operator✦ Claude Code

Claude 站在哪一层

本机终端 · 不在云电脑上

Claude 跑在 Mac 的终端里,不在云电脑内部。它拿着阿里云的 RAM 凭据,通过 CLI 把 PowerShell 脚本投进去、把结果捞回来——本质是一个会写脚本、会读回执、会自己纠错的运维

aliyun CLI · profile akke-wuying
Scope

本文讲的是控制通道——怎么让 Claude 可靠地在远端执行、确认、纠错。至于云电脑上那个循环本身怎么读屏、怎么生成话术,是另一条线的事。

02
The Control Chain · End to End

一条命令的完整旅程

从你在终端里说一句话,到云电脑上真的有个窗口被点了一下,中间要穿过七层。每一层都有它自己的失败模式,也各有各的证据。

CLAUDE
本机终端
Claude 写出一段 PowerShell,base64 编码后组装成 aliyun ecd run-command 命令行
RAM 凭据
阿里云 API
用受限 RAM key 调 ECD OpenAPI(2020-09-30)。接口是异步的——立刻返回一个 InvokeId,不等执行结果
云助手
Agent
云电脑里预装的云助手 agent 拉到任务,落盘成脚本文件准备执行
SESSION 0
SYSTEM
脚本以 nt authority\system 身份在 会话 0 里跑。这里看不见任何桌面——装依赖、写文件、改注册表可以,点按钮不行
计划任务
跨会话跳板
要碰 GUI,就在会话 0 里注册一个 InteractiveToken 计划任务,让它在已登录的会话 1 桌面里启动 Python
SESSION 1
真实桌面
pyautogui 截屏、定位、打字、点击。这一层才真正碰得到企业微信窗口
回执
读回本机
执行结果经 DescribeInvocations 回捞;业务是否真的发生,另查数据库落库记录(下详)

// 关键认知:会话 0 和会话 1 是两个世界。远程命令只能落在会话 0,GUI 只存在于会话 1,中间那道计划任务跳板是整条链路的枢纽。

0KB
单条脚本 base64 后的官方上限,决定了大文件必须分块
0
Timeout 默认值,pip 装依赖极易顶到这条线
0
一次调用可同时下发的云电脑数量上限
0KB
回执 Output 字段的截断线,超出部分记在 Dropped 里
03
API Surface · Ground Truth

两个接口,一张表说完

整套控制只依赖两个 OpenAPI:一个把脚本投进去,一个把结果捞回来。下表是官方文档口径,右列标注我们实际踩到的对应后果。

参数 / 字段官方口径实际影响
TypeRunPowerShellScript / RunBatScript 两种只有这两种,没有直接跑 Python 的类型——想跑 py 得用 PowerShell 包一层
ContentEncodingBase64PlainText(默认后者)中文脚本一律走 Base64,否则编码问题防不胜防
命令体大小base64 编码后不超过 16 KB硬墙。88 KB 的脚本没法一次投进去,必须压缩+分块
Timeout默认 300 秒pip 装依赖常年顶线,必须挂国内镜像源提速
DesktopId数组,单次 1 ~ 50 台多台批量部署可一次下发,但结果要逐台看
返回值异步,立刻返回 InvokeId发出去 ≠ 跑完了,必须轮询查状态
InvocationStatusPending / Running / Success / Failed / Stopping / Stopped / PartialFailed多台下发时 PartialFailed 是常态,别只看整体状态
ExitCode整型,逐台返回在 InvokeDesktops[]忠实反映脚本里的 exit N(见下一节实测)
Output仅当请求带 IncludeOutput=true 才返回最大的坑。不传这个参数,输出永远是空的——我们为此绕了半年远路
Output 截断超过 24 KB 截断,丢弃字符数记在 Dropped长日志要么自己截尾,要么写文件再分段捞
执行记录留存可查近 2 周,最多 10 万条事后复盘有窗口期,别指望翻上个月的执行史
投递 + 回捞 · 最小可用形态
# ① 把脚本 base64 后投进去 —— 注意 region 要写两遍,机器在哪个区就写哪个
B64=$(printf 'Write-Output "PROBE-OK $(hostname)"\nexit 7\n' | base64)
aliyun ecd run-command --api-version 2020-09-30 \
  --region cn-chengdu --biz-region-id cn-chengdu \
  --type RunPowerShellScript --content-encoding Base64 \
  --command-content "$B64" --desktop-id ecd-xxxxxxxx --timeout 60
# → {"InvokeId": "t-cd06rye9ua051q8"}

# ② 捞结果 —— --include-output true 是关键,不传它 Output 永远为空
aliyun ecd describe-invocations --api-version 2020-09-30 \
  --region cn-chengdu --biz-region-id cn-chengdu \
  --invoke-id t-cd06rye9ua051q8 --include-output true
# → InvocationStatus: "Failed"   ExitCode: 7   ErrorCode: "ExitCodeNonzero"
# → Output: "UFJPQkUtT0sg..."  (base64,解出来是 PROBE-OK <主机名>)
Region

--region--biz-region-id 两处都要写机器所在区。只改一处,接口会安静地返回「0 台机器」,看起来像机器不存在——这是新手第一天必踩的坑。

04
Session 0 vs Session 1

能执行,不代表看得见

Windows 从 Vista 起就把服务用户桌面隔离在不同会话里:服务跑在会话 0,第一个登录的人在会话 1。远程命令属于服务那一侧——它有最高权限,却连一个像素都截不到

✅ 会话 0 能干的

  • 装 Python、装依赖、写 .env
  • 下载 / 解压 / 校验文件
  • 读注册表、改系统配置、设开机自启
  • 静默安装 App、查进程、查网络连通性
  • 注册计划任务(跳到会话 1 的唯一入口)

✕ 会话 0 干不了的

  • 截屏——只会拿到全黑或兜底分辨率
  • 点击、打字、拖拽任何窗口
  • 看见企业微信/抖音客户端在哪
  • 弹窗交互、扫码登录
  • 读屏定位(视觉模型没有图可看)
跳板:InteractiveToken 计划任务

破法是让会话 0 注册一个登录类型为 InteractiveToken 的计划任务schtasks /create /xml,指定 <UserId>机器名\用户名</UserId>),它会用该用户的交互令牌在已登录的会话 1 桌面里启动进程——免密码、有 GUI、pyautogui 全套可用。前提是那台机器确实有人登录着query user 看到 console 会话在运行)。

用 pythonw 而不是 python

跳板里启动 GUI 脚本要用 pythonw.exe——它不开控制台窗口。用 python.exe 会弹一个黑窗口盖在企业微信上,紧接着截屏读到的就是那个黑窗口,整条视觉链当场失效。

唯一真人不可替代的一步

首次扫码登录。企业微信、抖音的账号登录必须有人拿手机扫一次。登录态建立之后,后面所有事都能自动化——所以一台机器的「开荒」是人工的,「常驻运维」是全自动的。

05
Readback Channels · A Correction

我们判死过三条通道,两条是自己按错了

这一节是整篇最值得读的部分——不是因为技巧,而是因为它是一次把自己的结论推翻的记录。写这页时顺手复测,结果和团队记忆里的三条「已知死路」全对不上。

The Old Belief

写在内部记忆里、被反复引用了三天的三条结论: 回执 Output 恒为空,「云助手吞了 stdout」; ExitCode 恒为 0,脚本里写 exit 3 也没用,不能用退出码编码任何诊断结果 这台机器的远程命令执行层已死,agent 只是假装成功、根本不执行脚本

2026-07-25 12:50 · 对照探针实测(两台机器各一次)
# 探针脚本:输出一行含主机名与当前时刻的文本,然后故意非零退出
Write-Output "PROBE-OK $(hostname) $(Get-Date -Format o)"
exit 7

# 回捞(带 --include-output true):
InvocationStatus : Failed
ExitCode         : 7          ← 脚本写 7,回来就是 7
ErrorCode        : ExitCodeNonzero
Output           : UFJPQkUtT0sgNjhxZjVyeGZjcHZ4cmwyIDIwMjYtMDctMjVUMTI6NTA6MjAu...
                   ↓ base64 解码
                   PROBE-OK 68qf5rxfcpvxrl2 2026-07-25T12:50:20.1608417+08:00

# 第二台(记忆里被判「执行层已死」的那台),改成 exit 3:
InvocationStatus : Failed   ExitCode : 3
Output (解码)     : PROBE-OK fbmvz3yvpvbiog0 2026-07-25T12:50:38.6871888+08:00

结论 ① · 推翻Output 不是被吞了

真因是请求没带 IncludeOutput=true。官方文档写得清清楚楚:不传这个参数就不返回该字段。我们把「参数没传」读成了「平台有 bug」,然后为此发明了一整套 ntfy 中转方案。

结论 ② · 推翻ExitCode 是准的

exit 7 回 7,exit 3 回 3,并附带 ErrorCode: ExitCodeNonzero。逐台的退出码在 InvokeDesktops[] 数组里,不在顶层——大概率当初读错了层级。

结论 ③ · 限定为一次性故障执行层没死

两台机器都返回了真实主机名和当前秒级时间戳——不可能是缓存或回放。当初那晚的「假装成功」是网络故障期的一次性现象,重启后已愈,不该被写成这台机器的常态属性。

读回通道说明该信到什么程度
数据库落库记录业务真的发生了才会有行——回复入库、发送状态、心跳计数■ 硬事实。任何结论以它为准
DescribeInvocations
(带 IncludeOutput)
脚本 stdout + 逐台 ExitCode + 错误码■ 可信,实测无误。注意 24 KB 截断
脚本主动 POST 出来让云电脑把结果推到消息服务或后端接口■ 可信但依赖出口网络,网络一抖就静默失联
人工远程桌面截图真人连上去看一眼■ 绝对可靠,但不可自动化,只作最后兜底
这次踩坑真正的教训

不是「哪个字段不能信」,而是——验证探针本身要先过对照组校准。当时确实做了对照实验(写一个必然失败的脚本,看它是否报失败),但探针自己也在故障链路里,于是得到一串误导性的「成功」,进而推出「回放缓存」「平台吞输出」等一连串错误根因。信道失真时,用失真信道做的所有诊断都要作废重来,而不是在上面继续叠推理。

06
File Transfer · 16 KB Pipe

怎么把 88 KB 塞进 16 KB 的管子

要更新的主脚本 88 KB,单条命令上限 16 KB,而这台机器的出口拉不到 GitHub、拉不到常见 paste 服务——外部中转全断。剩下唯一的路:把文件本身当命令内容送进去

原始 Python 脚本88 KB
gzip + base64 后43 KB
单条命令官方上限16 KB
实际每块写入9 KB

// 每块留足余量给 PowerShell 包装代码本身 → 43 KB ÷ 9 KB ≈ 5 块,一次成

本机
切块
git show origin/main:<file> | gzip | base64split -b 9000
逐块
下发
每块一条远程命令,Add-Content -NoNewline 往同一个 .gz.b64 文件追加
远端
还原
[Convert]::FromBase64String + GZipStream 解压出原文件
校验
再替换
MD5 与本机 md5 -q 逐字节比对,对上了才覆盖线上文件,然后重启进程
改 .env 千万别用 Set-Content -Encoding UTF8

它会在文件开头写一个 UTF-8 BOM。如果 .env 第一行恰好是个变量名,BOM 就把变量名污染成 ANTHROPIC_API_KEY,程序读不到、自检失败、进程死循环重启——而值本身完好无损,长度都没变,排查时极难看出来。同一条命令下发到两台机器,一台首行是注释就没事,另一台首行是变量名就炸,靠运气决定命运。正确写法是显式指定不带 BOM 的编码器。

07
Egress Network · SNI Blocking

TCP 通了,HTTPS 不通

云电脑的出口是国内直连、无代理。一批境外域名会出现一种很有迷惑性的症状:端口探测显示握手成功,但真正发 HTTPS 请求永远超时。这不是断网,是按域名的定向阻断。

SYMPTOM · 症状只有它一个超时

国内站点 200、自家海外后端 200、唯独某几个域名全线超时;而端口探测(TCP 层)却报成功。判据是:TCP 握手成功、TLS ClientHello 发出后被重置 — 典型的按 SNI 阻断,而不是链路不通。

FIX · 修法拿自己的服务器当跳板

已经能连通的自家海外服务上加一个 OpenAI 兼容的透传端点,把云电脑的调用改指过去:云电脑 → 自家东京节点 → 目标服务。凭据原样透传、中转方不持有,上游写死单一目标不是开放代理。客户端零改动,只换一个 base URL。

别急着归因到「机器坏了」

同一批症状我们经历过两次:第一次是真的定向域名阻断,修法是中转;第二次是本机级网络故障,表现几乎一样,修法是重启机器——重启后当场恢复。先分清「某个域名不通」和「所有出站都不通」,前者做中转,后者重启。用一个已知可达的自家端点当对照组,一次探测就能分开。

08
Field Notes · Everything That Bit Us

坑位速查表

按「症状 → 真因 → 修法」排列。第一次接手这条链路的人,把这张表看完能省掉大约两周。

症状真因修法
接口返回 0 台机器region 默认走了凭据配置里的区,看不到别的区--region--biz-region-id 都写机器所在区
Output 永远是空请求没带 IncludeOutput=true加上该参数;结果是 base64,记得解码
远程命令跑了,弹窗/点击全无效脚本在会话 0,看不见桌面GUI 步骤走 InteractiveToken 计划任务跳会话 1
GUI 脚本起来了但截屏是黑窗用了 python.exe,控制台窗口盖住目标改用 pythonw.exe
内嵌 Python 一行命令报语法错base64 → PowerShell → Python 多层引号绞杀写成 .ps1 文件执行;能用系统命令的别嵌 Python
pip 装依赖顶到超时默认源跨境慢挂国内镜像源,必要时调高 Timeout
脚本起不来,说找不到 python装 Python 时没勾「加入 PATH」用启动器 py(永在 PATH)或写绝对路径;会话 0 下环境变量还有缓存,改了要重启才生效
日志重定向到文件就崩日志里有 emoji,写文件时退回系统编码启动前置 PYTHONUTF8=1
中文日志回传全变问号PowerShell 默认按系统代码页,Python 输出是 UTF-8管道前显式转码,或整个会话切 UTF-8
改完 .env 一台好一台炸Set-Content -Encoding UTF8 写了 BOM,污染首行变量名用不带 BOM 的编码器;改前备份,改后必看自检行
常驻循环每轮全跳过分辨率严格相等检查,远程桌面重连会差几像素改成容忍 ±20px 的范围检查
新机器套老机器坐标全点偏客户端版本不同导致布局变化,分辨率相同也不行每台重新标定坐标,别复制粘贴
视觉模型把示例卡片读成真实消息截图裁剪只切了左边界,右侧侧栏的演示内容进了画面补一个右边界裁剪参数,把无关面板切掉
三台机器同时搜不到人已装客户端的机器做镜像克隆,设备指纹被一起复制基础镜像只装依赖不装客户端,克隆后逐台安装登录
09
Working With Claude · The Discipline

让 Claude 干这活,需要什么纪律

技术上这套链路并不复杂,难的是盲操作——Claude 看不到那块屏幕,只能靠回执推断发生了什么。要让它可靠,靠的不是更聪明的提示词,而是几条硬纪律。

RULE 01先校准探针,再信探针

任何用来判断远端状态的信道,先跑一个「必定失败」的对照脚本。它要是也报成功,这条信道当场作废,别在上面做任何推理。

RULE 02业务真伪只认数据库

「命令执行成功」和「消息真的发出去了」是两件事。前者问接口,后者只能查库里有没有那一行。所有对外声明以后者为准。

RULE 03改动前备份,改动后自检

远端没有撤销键。覆盖任何文件前先复制一份带日期的备份;替换后必须读回自检行确认服务真的起来了,而不是看到「命令返回成功」就宣布完成。

RULE 04把配方写成文档,不是记在脑子里

这条链路的每个参数、每个坑,都落成一份团队共享的记忆文件。新会话开工先读它——省下的不是打字,是重新踩一遍坑的两周

RULE 05过期的结论要主动推翻

本页第 05 节就是例子:三条被反复引用的「已知死路」,复测下来两条是自己参数按错、一条是一次性故障。写进文档的结论也会过期,定期复测比继续引用更重要

RULE 06一台机器一个会话

常驻循环会不停把目标窗口拉到前台点击。真人这时连上远程桌面操作,两边抢焦点,双输。要人工介入,先把循环停掉。

一句话概括这套配合

Claude 负责组装命令、解析回执、比对校验和、发现矛盾就停下来复测;人负责扫码登录、拍板参数、以及在信道全断时连上去看一眼。分工的界线不是「谁更聪明」,而是「这件事有没有确定性的答案」——有的交给代码,没有的才交给人。

10
Cost & Boundaries

什么时候值得这么干,什么时候不值

现状口径
机器2 台,成都可用区,均 6 核 12G、80G 系统盘、Windows 11eds.enterprise_office.6c12g
单机月成本约 ¥249/月(办公型主机 + 系统盘)包年包月
控制通道远程命令 + 计划任务跳板,本机 Claude Code 驱动ECD OpenAPI 2020-09-30
人工介入点首次扫码登录、坐标标定、信道全断时的兜底截图其余全自动
开荒耗时单台从空白到真发,约半天(含依赖、脚本、坐标、验证)复制到第二台快得多

✅ 值得上这套的场景

  • 目标平台没有可用的对外 API,只能走 GUI
  • 常驻 7×24,不能靠人守着
  • 机器数量会长到十几台以上,需要脚本化批量部署
  • 每台机器要绑独立账号身份,不能共用一个环境
  • 需要远程热更新脚本与配置,不想每台手动连进去

✕ 不值得的场景

  • 只有 1 ~ 5 台试点——手动连进去更快,别为自动化交学费
  • 目标平台有正经 API——那就去用 API,GUI 自动化是下策
  • 任务是一次性的——搭链路的成本远超省下的时间
  • 需要毫秒级可靠性——GUI 自动化天生脆,客户端一升级就要重标
  • 没人能接受首次登录必须人工这个前提
一句话总括:企业版无影比个人版多出来的,本质上就是一个能从外部投脚本进去的异步接口。围绕它搭一层「会话 0 装环境、计划任务跳会话 1 点鼠标、回执与数据库双通道验证」的骨架,一台云电脑就从「远程桌面里的一台机器」变成了Claude 能可靠操作的一个执行端。这套东西的难点从来不在 API,而在盲操作时如何建立可信的证据链——以及在证据自相矛盾时,敢于把自己三天前的结论推翻重测