退出码
每条 sow 命令都使用退出码 0–6 或中断码 130,对所有命令一致,
并且就是设计来给脚本做分支判断的 —— 区分"这件事失败了"和"这件事被正确地拒绝了",
正是设置多个非零码的全部意义。
| 码 | 含义 |
|---|---|
0 |
完整成功,或幂等 no-op |
1 |
运行时 I/O、解析器、渲染器或未知内部错误 |
2 |
用法、工作区发现或配置错误 |
3 |
部分成功:至少一项已提交,至少一项失败 |
4 |
写锁不可用 —— 被占用且指定了 --no-wait,或等待超时 |
5 |
完整性/恢复错误,或 check 判定当前结果不可交付 |
6 |
预期拒绝:冲突、protected、无匹配、架构不兼容 |
130 |
命令被 Ctrl-C(SIGINT)或 SIGTERM 中断 |
人类可读的结果写 stdout,警告与诊断写 stderr。每个码在 stderr 上有稳定的消息前缀,
在 JSON 输出中有对应的 class:
| 码 | stderr 前缀 | JSON class |
|---|---|---|
1 |
随子系统而异 | runtime |
2 |
usage error: / workspace discovery error: / configuration error: |
usage, discovery, config |
3 |
... batch partially succeeded |
partial |
4 |
lock unavailable: |
lock |
5 |
integrity or recovery error: |
integrity |
6 |
operation rejected: |
rejected |
130 |
随子系统而异 | interrupted |
sow create 是前缀那一列的例外:它不在 Managed 层内,stderr 上打印的是原始领域错误
(plain: scan …、plain: marker gate …),没有 CLI 类别前缀(例如 operation rejected:、
lock unavailable:)。该前缀仍然出现在它的 JSON errors[].message 里。
130 —— 中断
Ctrl-C(SIGINT)或 SIGTERM 取消命令并返回 130;普通等待锁超时仍返回 4。只有命令本身被中断才返回
130:内部超时(例如 R2 上传长时间没有进度)属于普通运行错误(1)。SOW 返回的是数字退出状态,不会向自身重发 SIGINT;父 shell 仍可能继续执行下一条命令。
脚本需要检查状态、使用 set -e,或自行处理信号。取消不会被报告成签名策略失败。
add 在 Desired 提交前被取消时,不会把任何输入报告为 accepted,且先持久化失败状态再清理
临时包字节;若清理未能在 5 秒预算内完成,剩余部分由下一条写命令(例如 sow build)删除,
在此之前 sow check 报告 recovering。已经提交的变更仍然保留,重跑命令即可恢复或继续收敛。操作完成前应保留原始输入包。
0 —— 成功或无操作
命令完成了你要求的事,或者发现无事可做。两者都算成功:对未变化的目录重跑
sow create,或对 clean 仓库执行 sow build,都返回 0 并如实说明。
"noop":true 才是区分"没干活"与"干了活"的依据,退出码不区分这两者。
sow status 是有意为之的特例:只要状态数据库可读,它在 clean、dirty、
recovering、error 四种状态下都返回 0 —— 让脚本去读结构化状态,
而不是从退出码反推。需要"闸门"时请用 sow check。
1 —— 运行时错误
I/O、解析或渲染层面出了问题:目录不可写、磁盘满、包读不出来。 这类是 环境问题,不是用法问题。
stage 目录之所以一开始就创建,正是为了让这类失败发生在 任何东西被发布之前。 本来就有合法索引的仓库,索引依然完好。
2 —— 用法、发现或配置错误
你要求的事情 CLI 无法执行:未知参数、目标不明确、找不到工作区,或 sow.yml 解析不通过。
什么都没有尝试执行。
未知参数:
互斥参数:
目标不明确 —— 仓库有两个 Dist,而命令需要确定其一:
当前目录向上找不到任何工作区 —— 错误会说明搜索位置与修复方式:
起点指向普通文件而不是目录时,搜索不会开始:
指向目录的符号链接可以作为发现起点,包括 macOS 上的 /tmp。发现仍按字面路径向上
搜索,未找到工作区时继续回退到 SOW_DIR。从起点隐式选择 Repository 与 Dist 的要求更严:
起点目录与工作区根之间只要有任一符号链接分量,就不能推断,此时请显式传入 -r。如果符号链接
位于 Repository 内部,会推断 Dist 的命令只给 -r 仍会失败,还要同时传入 -d。
配置文件格式有问题 —— 注意错误会指出具体行号:
sow.yml 的所有文法与 schema 错误都归到这一码。
3 —— 部分成功
一个批次里有的项已提交、有的项失败。这个码存在的意义是:你永远不必猜测一次失败的
sow add 是否让仓库毫发无损 —— 返回 3 就意味着合法的包 已经进去了,
失败的那些会被逐条点名。
失败的输入文件原地不动。加上 --json 时,已提交的项仍然完整列出 ——
非零退出 从不 截断 result:
sow init 在已提交了部分声明的仓库或 Dist、随后在后面某项上失败时,也用这个码。
4 —— 锁不可用
另一个进程持有写锁。SOW 在设计上就是单写者(single-writer), 所以这是 正常且预期 的结果 —— 重试,或者多等一会儿。
带 --no-wait 时立即失败:
带超时时,恰好等待这么久后失败:
-T 0(默认)一直等待。只读命令不取写锁,永远不会返回 4;
sow status 甚至把这种争用作为一个字段报告出来:
5 —— 完整性、恢复,或不可交付
两种不同的情况共用这个码,它们的含义都是"先别把这棵树发出去"。
常见的那种:仓库的期望状态领先于已构建的内容 —— sow add --skip 之后,
或者改了策略/签名之后,对它执行 sow check。每一层校验都通过,
仓库只是 尚未收敛:
解决办法是 sow build。这正是部署脚本应该拿来做闸门的码 ——
它区分的是"磁盘上的树完整且最新"与"磁盘上的树完整但过期"。
少见的那种是真正的完整性失败:状态数据库、journal 与文件树互相矛盾,
且 SOW 无法安全地自行裁决。此时它拒绝覆盖任何东西,你应该从备份恢复,而不是强行修复。
这里 有意 没有 --force。
6 —— 预期拒绝
命令写法正确、环境也没问题,是 SOW 主动判定"不行"。 这些是策略与安全决策,不是故障。
受保护的仓库:
匹配不到任何东西的引用:
有歧义的裸名 —— 候选会一并列出,方便你挑一个:
工作区不允许的架构。注意逐项错误会点名检测到的值,并告诉你去哪里改:
目录里没有任何可索引的包:
--pigsty 完成标记挡住了对既有构建的覆盖:
签名 key 引用文法正确但解析不出密钥 —— 文法错误是 2,解析失败是 6:
在脚本里使用
这些码的设计目标就是让部署流水线 不必解析文本 即可分支:
这里的 mirror 是 pigsty 已配置的 Publication Target。
两个值得养成的习惯:把 4 当作 可重试 而不是致命错误;
永远不要把 6 当作崩溃 —— 它通常意味着需要改的是你的输入,而不是 SOW。