本地能跑不等于环境里能跑
# 本地能跑不等于环境里能跑
结论
你反复踩的是同一个坑:「本地正常」被当成「环境正常」的证据。 但本地与环境跑的不是同一份东西 —— 不是同一段代码路径、不是同一套配置键、 不是同一份可变状态(库 / 缓存 / 凭据)。 判据一句话:把两侧的差异项列出来,差异项就是唯一需要验证的清单。
五处证据:每一处都是「两侧根本不是同一份」
| 出处 | 本地这一侧 | 环境那一侧 | 分叉点 |
|---|---|---|---|
| 本地开发脚本可能连生产资源 | 以为「只是本地开发」 | 实际连的是生产 Redis / 库 | 环境变量文件里写的就是生产值 |
| 本地测试要显式覆盖数据源 | .env.local(软链到主目录) | 远程 / 生产数据源 | 数据源没被显式覆盖 |
| 兼容端点要防SDK注入非标准字段 | 没配 *_BASE_URL → 走 SDK 内置原生 provider | 配了 relay → 走兼容层,被多塞一个字段 → 400 | 少一个环境变量就换了整条调用路径 |
| 集成已合并不等于运行时已启用 | 本地有一套 key → 能 trace | 容器 env 里一个都没有 → 静默 noop | 能力在缺配置时静默关闭 |
| 无入站流量的服务靠数据与配置隔离环境 | 另一个目录 / 另一个 compose 项目 | 连的仍是正式库 | 隔离看的是「共享了什么可变状态」 |
旁证:agent 程序本机就能跑,却每次都只能部署到云端验证 —— 因为它不会自己读 .env.local,要显式喂给它(clean/2026-07-29.md @ 21:36)。
综合判断
-
差异的来源不是「环境更差」,而是「环境里少了一个隐式默认」。 本地常见的隐式默认: 没配 base URL 就 fallback 到 SDK 原生 provider、Next.js 自动读
.env.local、框架给的默认值。 这些默认在环境里往往不存在,或指向别处 —— 于是同一份代码在两侧走了两条路。 -
这类分叉全部静默。 本地 200、线上 400;本地有 trace、线上零数据;本地连本地库、 线上连生产库。没有一条错误信息指向「你测的不是这一份」。
-
可迁移的动作:任何「本地验证过了」之后,追问三句 —— 「环境侧少了哪个默认?」「两侧的 provider 路径是同一段代码吗?」「两侧共享了哪些可变状态?」 把差异项列成清单,逐项在环境里验证一次。
-
环境隔离的判据不是「部署在哪」,而是「共享了什么可变状态」。 一个进程只要连了正式库, 就算部署在另一个目录、另一个 compose 项目里,也等于没有独立环境。
与既有 synthesis 的关系
- 判断现状要看真正在跑的那一份 —— 那条讲观测点没落在执行路径末端;本条讲两侧的执行路径 根本不同。前者是「看错了一份」,后者是「根本没有同一份」。
- 分层要落到运行时才有效 —— 配置也是分层的一种:写进文件但没人读(或读到的是另一份),就等于不存在。
- 存量不会自动跟上新逻辑 —— 同族:改动只覆盖「未来经过这条路径的对象」。
为什么这是 synthesis
单看每一页,都是某个项目里的一次环境事故(连了生产库、relay 400、线上零 trace)。 并排之后才看得出它们共享同一个失败形状:把「一侧能跑」当成了「两侧同构」。
相关
- 本地开发脚本可能连生产资源 / 本地测试要显式覆盖数据源 / 无入站流量的服务靠数据与配置隔离环境
- 兼容端点要防SDK注入非标准字段 / 集成已合并不等于运行时已启用 / 改配置后要确认部署真的用了新配置
- deploy目录是运行配方不是另一套应用