feat: 兼容oh my pi - #8183
Conversation
- 新增完整的 TailwindCSS 配置与主题变量定义 - 实现窗口控件相关的样式与自定义变体支持 - 添加暗黑模式及桌面运行环境专属样式 - 优化滚动条、聊天 Markdown 渲染及代码块样式 - 主入口 main.tsx 结构化,兼容 Electron 与浏览器环境 - 集成 Clerk 认证支持,自动选择 Electron 或 Web 版本 - 配置字体资源与全局样式导入,确保 UI 统一风格
- 引入 midscene/core 依赖,支持内置“midscene-preview”技能 - 新增 bundledSkills 模块,提供技能路径解析和 Codex 服务初始化封装 - 在 CodexProvider 和 CodexSessionRuntime 中替换初始化逻辑,调用封装函数初始化带技能支持的 Codex App Server - 在服务器构建脚本中复制 bundled-skills 目录至发布目录,确保技能资源包含在构建产物中 - 修改桌面应用命名,将“Chinese”标签更改为“Browser” - 更新预览管理器,增加基于视口坐标自动滚动的功能 - 新增 PreviewMidsceneToolkit 及其运行时和处理器,实现对内置浏览器的控制和验证能力 - 扩展 contracts 包,添加 previewMidscene 相关接口定义 - 更新 .gitignore,忽略 openspec 目录 - 维护测试覆盖,新增对技能加载及预览 midscene 工具包的单元测试
- 添加 Midscene 模型配置解析和环境变量覆盖逻辑,支持从服务器设置动态加载配置 - 允许在代理中揭示隐藏目标标签页,确保 Midscene 代理创建前标签页可见 - 在 PreviewMidscene 运行时中注入模型配置参数,支持多模型动态切换 - 服务器设置支持安全存储和隐匿 Midscene API Key,避免密钥泄露到客户端 - 新增并完善 Midscene 相关单元测试,覆盖配置应用及错误处理逻辑 - 在前端添加 Midscene 配置界面国际化文案(中英文) - 优化预览自动化的错误重试逻辑,增强 UnknownVizError 捕获与重试机制 - 修改 ChatView 组件,增加线程错误显示及隐藏功能,实现错误优先级和错误免打扰管理 - 修复 auth 模块中瞬态引导错误检测,支持因果链递归判定和传输错误判别 - 优化 Electron 预加载脚本导入,使用 contracts 新增的 preview-automation-features 模块导入方式 - PreviewAutomationHosts 中改进预览 Webview 可见性判定和等待逻辑,确保 DOM 渲染稳定后执行操作
为推理型 provider(Codex reasoning / Claude thinking / Pi thinking_delta / OpenCode reasoning)增加端到端的思维链展示能力。推理内容与 assistant 文本 共享同一消息 ID,以可折叠的「Thinking」区块在 UI 中渲染。 - contracts: OrchestrationMessage.reasoningText(可选)、ThreadMessageSentPayload.reasoning、 thread.message.assistant.reasoning.delta 命令 - decider/projector: 新增 reasoning delta 命令分支,流式合并 reasoningText (streaming 追加 / completion 替换) - ingestion: 提取 reasoning_text / reasoning_summary_text 增量,复用 createBufferedTextStore 缓冲逻辑,遵循 enableAssistantStreaming 投递模式 - 持久化: ProjectionThreadMessage 增加 reasoningText,SQLite upsert/SELECT 补 reasoning_text 列,新增 migration 033 - ProjectionPipeline / ProjectionSnapshotQuery: 投影与读取模型双向贯通 - UI: AssistantReasoningBlock 可折叠组件,流式时自动展开、完成后自动收起 - i18n: 中英文 chat.reasoning.* 键 验证: vp run typecheck / vp check 通过;ingestion 42/42、orchestration+ persistence 179/179、contracts+client-runtime+web 452/452 测试通过 Co-authored-by: traeagent <traeagent@users.noreply.github.com>
feat: 支持显示思维链功能
- 在右侧面板新增“上下文”选项卡支持上下文内容查看 - ChatComposer 组件支持打开上下文面板的回调属性传递 - ContextWindowMeter 组件添加打开上下文面板按钮 - 支持根据提供商 (codex、piAgent、opencode) 控制上下文面板可用性 - 服务端 ProviderAdapter 增加 readThreadContext 方法用于获取线程上下文原始消息 - CodexAdapter、OpenCodeAdapter、PiAdapter 分别实现原生上下文读取接口 - ProviderService 新增对应的线程上下文读取RPC接口及验证逻辑 - 升级 RPC 合约,增加 getThreadContext 方法支持上下文获取 - 消息时间线过滤函数改用 workEntryShouldBeVisible 以优化可见日志条目 - 添加上下文面板相关国际化文案,包括中文和英文 - 添加上下文面板相关单元测试覆盖子代理任务工作状态渲染 - rightPanelStore 增加 context 类型支持及对应状态存储版本升级 - RightPanelTabs 组件集成上下文面板入口及可用性控制 - server.test.ts 中 ProviderService 服务模拟添加以支持上下文相关测试环境
- 在简介中添加 Pi 供应商支持说明 - 更新安装说明,新增 Pi 供应商安装和认证步骤 - 修正并补充供应商指南链接,包括 Pi 的文档链接 - 新增桌面应用打包指南,涵盖 macOS、Linux 和 Windows 平台 - 详细列出各平台打包命令及对应架构说明
- Surface subagent lifecycle details and activity in the right panel - Preserve runtime event data and add coverage for status mapping � Conflicts: � apps/server/src/orchestration/projector.test.ts
- Retry transient electron-builder network failures up to two times - Add coverage for retry classification and limits
- Add OMP provider support across server, web, contracts, and documentation - Bundle provider runtime tests and settings integration
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Effect service conventions
This branch adds several new Effect service modules (apps/server/src/mcp/PreviewMidsceneService.ts, apps/server/src/mcp/preview-midscene/PreviewMidsceneRuntime.ts, apps/server/src/provider/pi/*, apps/server/src/bundledSkills.ts, apps/server/src/provider/Drivers/OmpDriver.ts). PreviewMidsceneRuntime.ts and OmpDriver.ts follow the conventions; four issues below are worth fixing in the new service code.
Note: the PR is 13.8k files against main because the fork branch carries a squashed upstream sync, so only the fork-introduced service modules were reviewed.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
UI consistency review
Three concrete issues in the changed web UI scope (apps/web/src). The rest of the web diff is mostly i18n string threading, which looks consistent with the shared primitives.
apps/web/src/index.css— the composer/context-strip glass composition was deleted and replaced by an opaque footer background.apps/web/src/components/ChatView.tsx— the bottom spacer lost its 1rem / 1.25rem breathing room, so the composer footer sits flush against the viewport bottom whereverenv(safe-area-inset-bottom)is0.apps/web/src/components/ContextPanel.tsx— the new refresh control is a raw<button>that recreatesButton, dropping the primitive's focus ring, pointer cursor, and disabled treatment.
Since these are visible, theme-sensitive layout changes, please pair the fix with light/dark evidence from the real composer (the PR's "UI Changes" section is still the template).
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3495ffa. Configure here.
| resolve, | ||
| upsertCommentForCurrentHead, | ||
| validateResult, | ||
| }; |
There was a problem hiding this comment.
Orphaned transfer report scripts
Low Severity
thread-transfer-report.cjs and its test are added, but no workflow under .github/workflows invokes them after CI was reduced to fork release only. The reporting helpers are dead code that will never publish PR comments or enforce ceilings in this tree.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 3495ffa. Configure here.
| expected, | ||
| ), | ||
| toThrow: (expected) => { | ||
| const { threw, error } = resolveThrown(actual); |
There was a problem hiding this comment.
🟡 Medium src/Expect.ts:619
expect(123).toThrow() and expect(new Error()).toThrow() pass even though no function was invoked, masking tests that failed to wrap the operation. resolveThrown treats every non-function as an already-thrown error; toThrow should require a function and fail for non-function values.
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/packages/alchemy-test/src/Expect.ts around line 619:
`expect(123).toThrow()` and `expect(new Error()).toThrow()` pass even though no function was invoked, masking tests that failed to wrap the operation. `resolveThrown` treats every non-function as an already-thrown error; `toThrow` should require a function and fail for non-function values.
Evidence trail:
Reviewed commit 3495ffae. `.repos/alchemy-effect/packages/alchemy-test/src/Expect.ts:419-429` treats all non-functions as thrown errors; `:618-629` passes when `threw` is true and `expected` is undefined. `https://v3.vitest.dev/api/expect.html` — `toThrowError` documentation states that the code must be wrapped in a function.
| const client = yield* HttpClient.HttpClient; | ||
| const res = yield* client.get(`https://${endpoint}/`, { | ||
| headers: AWS.Lambda.microvmAuthHeaders(authToken), | ||
| }); | ||
| return yield* res.text; | ||
| }); |
There was a problem hiding this comment.
🟡 Medium src/microvm-worker.ts:65
rawReachable records a MicroVM as ready for any HTTP response, including unauthorized, 404, or 500 responses, so the raw baseline can report a false readyMs. HttpClient does not fail on non-2xx responses; check res.status and the expected ok body before allowing the readiness retry to succeed.
const client = yield* HttpClient.HttpClient;
- const res = yield* client.get(`https://${endpoint}/`, {
+ const res = yield* client.get(`https://${endpoint}/`, {
headers: AWS.Lambda.microvmAuthHeaders(authToken),
});
- return yield* res.text;
+ const body = yield* res.text;
+ if (res.status !== 200 || body !== "ok") {
+ return yield* Effect.fail(
+ new Error(`readiness probe failed: ${res.status}: ${body.slice(0, 120)}`),
+ );
+ }
+ return body;Also found in 1 other location(s)
.repos/alchemy-effect/benchmark/container/src/orchestrator.ts:73
rawReachabletreats every HTTP response as a successful readiness probe: afterclient.get(...), it only readsres.textand never checksres.statusor the expected body.HttpClientpreserves non-2xx responses rather than failing them, so an unauthorized/404/500 response from the MicroVM proxy or server ends the retry and records a falsereadyMs, making the raw baseline results invalid.
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/benchmark/container/src/microvm-worker.ts around lines 65-70:
`rawReachable` records a MicroVM as ready for any HTTP response, including unauthorized, 404, or 500 responses, so the raw baseline can report a false `readyMs`. `HttpClient` does not fail on non-2xx responses; check `res.status` and the expected `ok` body before allowing the readiness retry to succeed.
Evidence trail:
Reviewed commit 3495ffae. `.repos/alchemy-effect/benchmark/container/src/microvm-worker.ts:57-70, 102-119, 199-210`; `.repos/effect-smol/packages/effect/src/unstable/http/HttpClient.ts:56-61, 83-90, 564-570`. Git commands: `git show 3495ffae -- .repos/alchemy-effect/benchmark/container/src/microvm-worker.ts`; `git show 3495ffae:.repos/effect-smol/packages/effect/src/unstable/http/HttpClient.ts`.
Also found in 1 other location(s):
- .repos/alchemy-effect/benchmark/container/src/orchestrator.ts:73 -- `rawReachable` treats every HTTP response as a successful readiness probe: after `client.get(...)`, it only reads `res.text` and never checks `res.status` or the expected body. `HttpClient` preserves non-2xx responses rather than failing them, so an unauthorized/404/500 response from the MicroVM proxy or server ends the retry and records a false `readyMs`, making the raw baseline results invalid.
| plt.close(fig) | ||
|
|
||
|
|
||
| all_max = max(max(by_key[k]) for _, k, _ in series) |
There was a problem hiding this comment.
🟡 Medium scripts/plot-blog.py:94
When a valid CSV has no successful samples for a key in series, all_max raises ValueError: max() arg is an empty sequence, so the script produces no plots despite other series having data. Filter unavailable base series before calculating the maximum and handle the case where none remain.
+series = [s for s in series if by_key[s[1]]]
-all_max = max(max(by_key[k]) for _, k, _ in series)
+all_max = max((max(by_key[k]) for _, k, _ in series), default=0)🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py around line 94:
When a valid CSV has no successful samples for a key in `series`, `all_max` raises `ValueError: max() arg is an empty sequence`, so the script produces no plots despite other series having data. Filter unavailable base series before calculating the maximum and handle the case where none remain.
Evidence trail:
.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py:18-25, 61-67, 94-95 (commit 3495ffa)
| // validates it and fails the cluster with "missing one or more required | ||
| // dependencies" otherwise. Rendered locally and applied to the EKS | ||
| // cluster before the HyperPod cluster attaches. | ||
| const chart = yield* FetchHyperPodChart({ |
There was a problem hiding this comment.
🟡 Medium src/eks-infra.ts:137
A clean first deployment fails at FetchHyperPodChart before the chart is rendered because git clone targets .alchemy/cache/hyperpod-cli without a pre-existing .alchemy/cache parent. Create the cache directory before cloning (or otherwise ensure the parent exists).
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/examples/aws-hyperpod/src/eks-infra.ts around line 137:
A clean first deployment fails at `FetchHyperPodChart` before the chart is rendered because `git clone` targets `.alchemy/cache/hyperpod-cli` without a pre-existing `.alchemy/cache` parent. Create the cache directory before cloning (or otherwise ensure the parent exists).
Evidence trail:
.repos/alchemy-effect/examples/aws-hyperpod/src/hyperpod-chart.ts:52-67 (commit 3495ffa)
.repos/alchemy-effect/examples/aws-hyperpod/src/eks-infra.ts:137-143 (commit 3495ffa)
.repos/alchemy-effect/packages/alchemy/src/AlchemyContext.ts:33-45 (commit 3495ffa)
Git clone documentation: https://git-scm.com/docs/git-clone
| ) => Effect.Effect<unknown, any, any>; | ||
| }; | ||
|
|
||
| export default Cloudflare.Worker( |
There was a problem hiding this comment.
🔴 Critical src/microvm-worker.ts:53
The public Cloudflare.Worker lets any caller invoke /boot without authentication or rate limiting, and each request runs AWS.Lambda.RunMicrovm for a fresh VM. An internet caller can therefore exhaust the account's MicroVM quota and incur AWS charges; require access control and/or rate limiting before launching VMs.
Also found in 1 other location(s)
.repos/alchemy-effect/benchmark/container/src/orchestrator.ts:63
url: truecreates a public Lambda Function URL, but this handler has no authentication or rate limit beforebootlaunches a fresh MicroVM. Anyone who discovers the URL can repeatedly call/boot(and choose among all six images), consuming the account's MicroVM quota and incurring AWS compute/build-related usage; this benchmark endpoint needs an access control mechanism rather than exposing the launch operation anonymously.
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/benchmark/container/src/microvm-worker.ts around line 53:
The public `Cloudflare.Worker` lets any caller invoke `/boot` without authentication or rate limiting, and each request runs `AWS.Lambda.RunMicrovm` for a fresh VM. An internet caller can therefore exhaust the account's MicroVM quota and incur AWS charges; require access control and/or rate limiting before launching VMs.
Evidence trail:
.repos/alchemy-effect/benchmark/container/src/microvm-worker.ts:53-56, 167-186, 212-215, 220-227, 237-245 (commit 3495ffa); .repos/alchemy-effect/benchmark/container/alchemy.run.ts:18-22, 52-58 (commit 3495ffa); .repos/alchemy-effect/packages/alchemy/src/Cloudflare/Workers/Worker.ts:353-356 (commit 3495ffa); .repos/alchemy-effect/packages/alchemy/src/AWS/Lambda/MicrovmImage.ts:474-478, 519-523 (commit 3495ffa); .repos/alchemy-effect/benchmark/container/test/bench.test.ts:31-34, 53-59 (commit 3495ffa).
Also found in 1 other location(s):
- .repos/alchemy-effect/benchmark/container/src/orchestrator.ts:63 -- `url: true` creates a public Lambda Function URL, but this handler has no authentication or rate limit before `boot` launches a fresh MicroVM. Anyone who discovers the URL can repeatedly call `/boot` (and choose among all six images), consuming the account's MicroVM quota and incurring AWS compute/build-related usage; this benchmark endpoint needs an access control mechanism rather than exposing the launch operation anonymously.
| fsModule.write = ((fd: unknown, data: unknown, ...rest: Array<unknown>) => { | ||
| if (fd === 1 || fd === 2) { | ||
| sink(`stray fd${fd}`, data); | ||
| const callback = rest.findLast((arg) => typeof arg === "function") as | ||
| | ((err: Error | null, written: number, data: unknown) => void) | ||
| | undefined; | ||
| callback?.(null, 0, data); | ||
| return; |
There was a problem hiding this comment.
🟠 High src/StrayOutput.ts:177
Intercepted asynchronous fs.write calls report written = 0 and invoke the callback synchronously, so callers that advance by the reported byte count can retry indefinitely or treat the write as incomplete. Report the accepted chunk's byte count and invoke the callback asynchronously.
fsModule.write = ((fd: unknown, data: unknown, ...rest: Array<unknown>) => {
if (fd === 1 || fd === 2) {
sink(`stray fd${fd}`, data);
+ const written =
+ typeof data === "string"
+ ? Buffer.byteLength(data)
+ : ((data as Uint8Array).byteLength ?? 0);
const callback = rest.findLast((arg) => typeof arg === "function") as
| ((err: Error | null, written: number, data: unknown) => void)
| undefined;
- callback?.(null, 0, data);
+ queueMicrotask(() => callback?.(null, written, data));
return;
}🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts around lines 177-184:
Intercepted asynchronous `fs.write` calls report `written = 0` and invoke the callback synchronously, so callers that advance by the reported byte count can retry indefinitely or treat the write as incomplete. Report the accepted chunk's byte count and invoke the callback asynchronously.
Evidence trail:
.repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts:153-191, commit 0b0d174; .repos/alchemy-effect/packages/alchemy-test/src/cli.ts:188-205, commit 0b0d174; Node.js fs.write documentation: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback
| if (options !== undefined) { | ||
| const stdio = options.stdio; | ||
| if (stdio === "inherit") { | ||
| options.stdio = ["inherit", "pipe", "pipe"]; |
There was a problem hiding this comment.
🟠 High src/StrayOutput.ts:217
Reusing an options object causes later childProcess.spawn and Bun.spawn calls to lose their capture pumps: the first call changes caller-owned stdio, stdout, and stderr values to "pipe", so subsequent children inherit undrained pipes and can block once the pipe fills. Clone the options and stdio configuration before rewriting these fields.
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts around line 217:
Reusing an `options` object causes later `childProcess.spawn` and `Bun.spawn` calls to lose their capture pumps: the first call changes caller-owned `stdio`, `stdout`, and `stderr` values to `"pipe"`, so subsequent children inherit undrained pipes and can block once the pipe fills. Clone the options and stdio configuration before rewriting these fields.
Evidence trail:
.repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts:214-238 and 252-284 @ 3495ffa
https://nodejs.org/api/child_process.html#optionsstdio
https://nodejs.org/api/child_process.html#subprocessstdout
https://bun.sh/reference/bun/spawn
https://bun.sh/docs/guides/process/spawn-stdout
| fig, ax = plt.subplots(figsize=(9.5, 4.8)) | ||
| for label, key, color in series: | ||
| xs = sorted(by_key[key]) | ||
| ys = [(i + 1) / len(xs) * 100 for i in range(len(xs))] |
There was a problem hiding this comment.
🟡 Medium scripts/plot-blog.py:141
The plots overstate completeness by showing only successful boots: failed rows are filtered out at line 20, so a 24/25-success run renders 24 strip points and a CDF that reaches 100% instead of 96%. Preserve the total boot count per key before filtering, use it as the CDF denominator, and indicate failed boots in the strip plot.
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py around line 141:
The plots overstate completeness by showing only successful boots: failed rows are filtered out at line 20, so a 24/25-success run renders 24 strip points and a CDF that reaches 100% instead of 96%. Preserve the total boot count per key before filtering, use it as the CDF denominator, and indicate failed boots in the strip plot.
Evidence trail:
Reviewed commit 3495ffae7efc: .repos/alchemy-effect/benchmark/container/scripts/plot-blog.py:17-25, 72-81, 137-145; .repos/alchemy-effect/benchmark/container/test/bench.test.ts:50-61, 94-105, 132-183. Verify with `git show 3495ffae7efc:.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py` and `git show 3495ffae7efc:.repos/alchemy-effect/benchmark/container/test/bench.test.ts`.
| // 2. global console — bun's console writes natively, NOT via stdout.write | ||
| // ------------------------------------------------------------------------- | ||
| try { | ||
| const methods = [ |
There was a problem hiding this comment.
🟡 Medium src/StrayOutput.ts:115
console.assert, console.count, console.timeEnd, console.timeLog, and console.clear still write directly to the terminal during capture, so those calls can interleave reporter output or corrupt the TUI alternate screen. Add these output-producing methods to the patch list (or proxy the complete Console API).
🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts around line 115:
`console.assert`, `console.count`, `console.timeEnd`, `console.timeLog`, and `console.clear` still write directly to the terminal during capture, so those calls can interleave reporter output or corrupt the TUI alternate screen. Add these output-producing methods to the patch list (or proxy the complete `Console` API).
Evidence trail:
Commit 3495ffae: .repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts:112-151; .repos/alchemy-effect/packages/alchemy-test/src/cli.ts:188-205; .repos/alchemy-effect/packages/alchemy-test/src/Tui.ts:443-459. Bun documentation: https://bun.com/reference/globals/Console, https://bun.com/reference/globals/Console/clear, https://bun.com/docs/runtime/console
| density = gaussian_kde(xs, bw_method=0.25)(np.log10(grid)) | ||
| ax.plot(grid, density, color=color, linewidth=2.2, label=label) | ||
| ax.fill_between(grid, density, color=color, alpha=0.15) |
There was a problem hiding this comment.
🟡 Medium scripts/plot-blog.py:120
The script raises instead of producing the remaining plots when an oc_series key has fewer than two successful samples or identical timings. gaussian_kde cannot estimate a density for those inputs because the sample is insufficient or has singular covariance; skip the KDE curve for degenerate sample sets.
- density = gaussian_kde(xs, bw_method=0.25)(np.log10(grid))
- ax.plot(grid, density, color=color, linewidth=2.2, label=label)
- ax.fill_between(grid, density, color=color, alpha=0.15)
+ if len(xs) >= 2 and np.ptp(xs) > 0:
+ density = gaussian_kde(xs, bw_method=0.25)(np.log10(grid))
+ ax.plot(grid, density, color=color, linewidth=2.2, label=label)
+ ax.fill_between(grid, density, color=color, alpha=0.15)🤖 Copy this AI Prompt to have your agent fix this:
In file @.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py around lines 120-122:
The script raises instead of producing the remaining plots when an `oc_series` key has fewer than two successful samples or identical timings. `gaussian_kde` cannot estimate a density for those inputs because the sample is insufficient or has singular covariance; skip the KDE curve for degenerate sample sets.
Evidence trail:
Commit 0b0d174. `.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py:17-25` filters and groups only successful samples; `:101-108` defines `oc_series`; `:116-135` unconditionally constructs/evaluates `gaussian_kde` before the CDF at `:137-156`. SciPy documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.html (does not support data lying in a lower-dimensional subspace). Verification command: `git show 0b0d174:.repos/alchemy-effect/benchmark/container/scripts/plot-blog.py`.
ApprovabilityVerdict: Skipped Macroscope did not run approvability analysis for this PR. This PR modifies Macroscope's approvability configuration ( Not approved because:
|


What Changed
Why
UI Changes
Checklist
Note
High Risk
Removing the main CI workflow and replacing upstream release/deploy automation with a fork-only release path is a major operational change; mistaken merges could stop checks and production deploys on the main repo.
Overview
This PR reshapes fork maintenance and agent workflows more than product runtime code. It adds a shared
.agents/skillstree (web/mobile test playbooks, iOS Simulator MCP debugging,pair-client.sh) and wires Claude/Codex to those skills plus pinnedxcodebuildmcp@2.6.2in.mcp.json/.codex/config.toml.GitHub automation is heavily replaced: several upstream workflows (
ci.yml, relay deploy, EAS, PR size/vouch, issue-label sync) are removed, andrelease.ymlbecomes a “Fork Release” pipeline (daily nightly schedule, standard GitHub-hosted runners, desktop artifacts + GitHub release only—no npm CLI publish, Vercel web deploy, relay config job, or Discord announce). New thread-transfer comment tooling (thread-transfer-report.cjs+ tests) and a triage playbook /via-triageissue template support agent-filed bugs; feature requests move to Discussions andfeature_request.ymlis dropped.Contributor defaults shift:
.env.examplenow documents production Clerk/relay public IDs by default,.gitignorecovers showcase/mobile artifacts, stale.plans/*docs are deleted, and Macroscope gains a UI consistency check agent. The PR title mentions Oh My Pi compatibility, but this diff does not touchOmpDriveror provider code—only infra/docs/agent tooling here.Reviewed by Cursor Bugbot for commit 3495ffa. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add compatibility with oh my pi in web app and worker
📊 Macroscope summarized 3495ffa. 137 files reviewed, 343 issues evaluated, 320 issues filtered, 15 comments posted
🗂️ Filtered Issues
.repos/alchemy-effect/benchmark/container/src/microvm-worker.ts — 2 comments posted, 4 evaluated, 2 filtered
Schedule.exponential("250 millis")for 14 attempts but has no timeout. From the included schedule implementation this accumulates roughly 68 minutes of backoff; afterRunMicrovmsucceeds, a failed readiness probe can therefore leave the request hanging for that duration and delay theonErrortermination, leaking a running MicroVM and tying up the Worker request. [ Failed validation ]/shutdownhandler suppresses everyTerminateMicrovmfailure withEffect.ignoreand still returns{ ok: true }. A transient AWS error therefore leaves the VM running while the driver believes cleanup succeeded; repeated benchmark rounds can accumulate leaked MicroVMs until the account quota is exhausted and subsequent samples fail or become contaminated. [ Failed validation ].repos/alchemy-effect/benchmark/container/src/orchestrator.ts — 0 comments posted, 4 evaluated, 4 filtered
url: truecreates a public Lambda Function URL, but this handler has no authentication or rate limit beforebootlaunches a fresh MicroVM. Anyone who discovers the URL can repeatedly call/boot(and choose among all six images), consuming the account's MicroVM quota and incurring AWS compute/build-related usage; this benchmark endpoint needs an access control mechanism rather than exposing the launch operation anonymously. [ Cross-file consolidated ]rawReachabletreats every HTTP response as a successful readiness probe: afterclient.get(...), it only readsres.textand never checksres.statusor the expected body.HttpClientpreserves non-2xx responses rather than failing them, so an unauthorized/404/500 response from the MicroVM proxy or server ends the retry and records a falsereadyMs, making the raw baseline results invalid. [ Cross-file consolidated ]v.reachableuses uncappedSchedule.exponential("250 millis"): 14 retries can wait about 68 minutes, while this Lambda is configured with a 120-second timeout. If the probe does not become reachable, Lambda stops the invocation before theEffect.onErrorcleanup can callv.term, leaving the successfully launched MicroVM running and causing later benchmark boots to hit quota. [ Cross-file consolidated ]/shutdownhandler suppresses everyTerminateMicrovmfailure withEffect.ignoreand still returns{ ok: true }. A transient AWS error therefore leaves the VM running while the driver believes cleanup succeeded; repeated benchmark rounds can accumulate leaked MicroVMs until the account quota is exhausted and subsequent samples fail or become contaminated. [ Cross-file consolidated ].repos/alchemy-effect/packages/alchemy-test/src/StrayOutput.ts — 6 comments posted, 8 evaluated, 1 filtered
toTextis called independently for every intercepted chunk, so a UTF-8 character split across twoBuffer/Uint8Arraywrites is decoded as replacement characters instead of being reconstructed. For example, two writes containing the first byte and remaining bytes of€produce corrupted log text. Keep a streaming decoder per capture (or buffer incomplete trailing sequences) when diverting byte output. [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy-test/src/Tui.ts — 1 comment posted, 7 evaluated, 6 filtered
text.trim().length > 1. A legitimate one-character selection (for example selecting a single glyph or character in the detail pane) is non-empty but is silently ignored, even though the comment says only a zero-width click should be excluded. The check should distinguish empty text from a one-character selection. [ Out of scope (post-validation triage) ]String.length/sliceagainstrenderer.terminalWidth, which is measured in terminal cells. A test title or path containing an emoji can be cut between its surrogate pair at the width boundary, producing a lone surrogate/replacement glyph (and wide Unicode text is otherwise mis-sized), so the TUI's claimed width clipping corrupts labels for valid Unicode names. [ Out of scope (post-validation triage) ]skiportodorow and pressingrreachescontroller.retryTest, but the runner never indexes skipped tests intestIndex, so the controller silently does nothing. The TUI still flashesretrying ..., misleading the user and providing no way to run that row; these statuses should be rejected or handled explicitly. [ Out of scope (post-validation triage) ]rcallscontroller.retryFile, but the runner'stestIndexcontains no entries for a file that never collected/runs successfully. The call is therefore a silent no-op while the footer flashesretrying <file>, so the TUI falsely offers retry for the most important file-level failures. [ Out of scope (post-validation triage) ]qhandler resolvesquitimmediately, but the only consumer (waitForExit) is invoked by the CLI afterrun()has already emittedRunEnd. Therefore pressing the advertisedqquit key during collection or test execution does nothing until the entire run finishes; the TUI cannot be exited early (apart from Ctrl+C), despite the footer showingq quitthroughout the run. [ Out of scope (post-validation triage) ]RunEnd,state.summaryis defined, so the interval stops callingupdateFooter()onceflashUntil + 100has elapsed. Any post-run action such asyorrsets a flash message, but the footer then remains permanently stuck oncopied to clipboard/retrying ...instead of reverting to the toggle bar as documented byflash. [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy/src/AWS/ACM/Certificate.ts — 1 comment posted, 2 evaluated, 1 filtered
regionprop allows a certificate to be requested in any AWS region, andreconcilecan create one there viawithCertRegion(news.region). However,listonly enumeratesus-east-1and the ambientRegion; a certificate deployed with (for example)region: "eu-west-1"while the stack runs inus-west-2is never returned. Account-wide consumers such asalchemy unsafe nuketherefore cannot discover or delete that certificate, leaving it orphaned after state loss. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/ACMPCA/CertificateAuthority.ts — 0 comments posted, 6 evaluated, 6 filtered
listCaTagsconverts every tag-listing failure—not just a missing CA—into{}. When the cached ARN is unavailable,findManagedCatherefore interprets a transientListTagstimeout, throttling error, or permission error as “no ownership tags” andreconcileproceeds tocreateCertificateAuthority. An already-managed CA can consequently be duplicated and left orphaned (and continue incurring Private CA charges); ownership lookup should fail rather than create on an indeterminate tag read. [ Exceeded comment limit ]waitUntilCreatedtreats every status other thanCREATINGas a successful observation. ACM PCA can transition fromCREATINGtoFAILED; in that casereconcilecontinues through tag sync and returns attributes withstatus: "FAILED"instead of failing or recreating the CA. The engine then records the failed, unusable CA as successfully provisioned and subsequent reconciles keep reusing it. [ Exceeded comment limit ]ResourceNotFoundExceptionas completion:describeCareturnsundefined, thenwaitUntilCreatedreturns it successfully andreconcilesubstitutes{ Arn: arn }. During the normal eventual-consistency window after creation, this skips the wait and immediately performs tag operations against an ARN thatDescribeCertificateAuthoritystill cannot see, causing the deployment to fail instead of retrying until the CA is observable. [ Exceeded comment limit ]createCertificateAuthorityrequest omits ACM PCA'sIdempotencyToken. If the request is accepted by AWS but its response is lost (or the SDK retries a transient failure), each retry can create another CA before the tag-based recovery search runs. The extra CA is not represented by the returned ARN/state and remains an orphan that continues billing; derive a stable token from the resource instance when creating it. [ Exceeded comment limit ]expirationomitted sendsExpirationInDays: undefined, while AWS stores/returns the default 7 days; similarly omittedCrlTypeis returned asCOMPLETEand omittedS3ObjectAclasPUBLIC_READ.JSON.stringifytherefore differs on every reconcile, so an ACTIVE/DISABLED CA receivesUpdateCertificateAuthorityon every deploy even though its configuration already matches the requested defaults. [ Exceeded comment limit ]canSetWindowomits theEXPIREDstate, even though AWS allows an expired CA to be deleted with a 7–30 day restoration period. When a managed CA has expired andolds.permanentDeletionTimeis configured, this branch sendsPermanentDeletionTimeInDays: undefined, silently ignoring the requested retention window (and falling back to AWS's default rather than the user's value). [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy/src/AWS/AIOps/InvestigationGroup.ts — 0 comments posted, 7 evaluated, 7 filtered
toAttributesonly returnsname,arn,roleArn, andretentionInDays, whilereconciletreatsencryptionConfiguration,tagKeyBoundaries,chatbotNotificationChannel,isCloudTrailEventHistoryEnabled,crossAccountConfigurations, the resource policy, and tags as managed state.Synccompares the result ofreadto the persisted attributes and skipsreconcilewhen they are equal, so out-of-band drift in any of those fields (for example a changed policy or removed ownership tag) is invisible and is never repaired unless some separately represented attribute changes. [ Exceeded comment limit ]undefinedwhile explicit"90 days"becomes90. Changing between those equivalent configurations therefore triggers a delete-first replacement, causing an unnecessary outage and new ARN instead of treating the effective retention as unchanged. [ Exceeded comment limit ]created.arn === undefinedbranch,observe(name, undefined)is allowed to returnundefined; the code then computesarnaslive?.arn ?? output?.arn, where both are undefined on a first create, but still returnsarn!and commits it. A transiently not-yet-listable newly created group therefore reports success without an ARN, skips policy/tag sync, and leaves later reads/recovery without the identifier needed to manage it. [ Failed validation ]ConflictExceptionrecovery accepts any resource returned byobserve(name, undefined)as the newly-created group without checking its Alchemy ownership tags. If this reconcile's initial observe sees no group and another stack/operator creates a same-named group beforecreateInvestigationGroup, the handler adopts that foreign group, then the later sync unconditionally updates its role/configuration, policy, and tags. This bypasses the ownership check performed byreadand can overwrite another stack's investigation settings or access policy. [ Exceeded comment limit ]encryptionConfigurationand the user removes that prop, thenews.encryptionConfiguration !== undefinedguard prevents resetting it to the documented AWS-owned key. Likewise, changingisCloudTrailEventHistoryEnabledfromfalseto omitted leaves CloudTrail history disabled instead of restoring its documentedtruedefault. These prop changes can reachreconcilebut silently do nothing. [ Exceeded comment limit ]JSON.stringify(JSON.parse(observedPolicy))with the desired string, which is sensitive to object key ordering (and to equivalent scalar-versus-singleton-array forms). A semantically identical policy returned with a different key order is therefore treated as drift and re-written on every reconciliation, causing needless policy mutations and possible throttling rather than converging to a no-op. [ Out of scope (post-validation triage) ]deleteFirstreplacements, butdeletereturns immediately afterdeleteInvestigationGroupis accepted and never waits for the singleton to disappear.Applyinvokes this delete and then immediately callsreconcilefor the replacement; while the old group is still visible, the new create getsConflictException, and this handler only re-observes the desired name (which is absent), rethrows the conflict, and leaves the replacement failed. The provider therefore cannot reliably perform the replacement it explicitly requires. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AMP/AnomalyDetector.ts — 0 comments posted, 2 evaluated, 2 filtered
waitSettledstops polling for any status other thanCREATING/UPDATING, so terminal failure statuses such asCREATION_FAILEDorUPDATE_FAILEDare treated as successful settlement. After a create or put reaches one of those states,reconcilestill tags the failed detector, notes the session, and returns its attributes, making a failed provisioning/update appear successful instead of surfacing the failure. [ Exceeded comment limit ]desiredTagslets user-providednews.tagsoverwrite the ownership tags fromcreateInternalTags. A resource configured with a tag such as"alchemy::id": "other"is created/synchronized without the real logical-id tag;readthen callshasAlchemyTagsand returnsUnowned, so the provider can no longer recognize its own detector (and may refuse to manage it or try to create another one after state loss). Reserved ownership keys must take precedence over user tags. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AMP/Scraper.ts — 0 comments posted, 5 evaluated, 5 filtered
olds !== undefined(lines 260-264), but the provider lifecycle explicitly supports adoption withoutput !== undefinedandolds === undefined. When an existing owned scraper is adopted and its EKS/VPC source differs from the desirednews.source,diffemits no replacement andreconcilehas no source update path, so the deployment succeeds while continuing to scrape from the wrong source. The source must be included in the observed attributes or adoption must force/perform replacement. [ Failed validation ]desiredTagsspreads usernews.tagsafter the ownership tags, allowing a user tag namedalchemy::stack,alchemy::stage, oralchemy::idto overwrite the value returned bycreateInternalTags. The scraper is created with the overwritten value, then the nextreadfailshasAlchemyTagsand brands the still-live scraperUnowned, so subsequent updates/destroy operations refuse to manage it and leave the scraper orphaned. Internal ownership keys need to be reserved or merged after user tags. [ Exceeded comment limit ]reconciletreatsoutput?.scraperId === undefinedas proof that the scraper is absent and immediately callscreateScraper(lines 285-305). The provider contract explicitly allowsoutputto be missing after a successful API call whose state write failed, and this resource already stamps unique Alchemy tags. A retry in that state creates a second scraper instead of locating the existing tagged scraper, leaving the first scraper running and duplicating metric collection/writes; repeated retries can accumulate orphan scrapers. [ Exceeded comment limit ]reconcileonly invokeswaitActiveafter a create or an update. If an existing scraper is already in a terminal failure state (for example after a prior interrupted/failed operation) and its fields otherwise matchnews, the code reads its blob, skips the update, syncs tags, notes success, and returnsstatus: "*_FAILED"without failing or recreating it. The provider therefore reports a successful deployment while the scraper is not collecting metrics. [ Exceeded comment limit ]roleDriftsis explicitly false whenevernews.roleConfigurationis omitted (line 324), even if the observed scraper has a cross-accountroleConfiguration. Consequently, changing a declared scraper from cross-account roles to the documented service-linked-role mode with no simultaneous alias/config/destination change causes noUpdateScrapercall and leaves the old roles active forever, so the cloud state does not match the resource props. AWS documents that switching back requires an update withoutRoleConfiguration. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AMP/Workspace.ts — 0 comments posted, 8 evaluated, 8 filtered
toAttrsomits bothretentionPeriod/wire retention andlimitsPerLabelSet, andreadreturns only these attributes. Consequently the engine's normal deploy/sync drift check cannot observe an out-of-band retention or label-limit change: the persisted attributes remain equal, soreconcileis never called and the desired configuration is silently left drifted. The configuration state must be included in the observable attributes or otherwise consulted during read/drift detection. [ Exceeded comment limit ]toAttrs, so tag drift is invisible to the engine. If an operator removes or changes a workspace tag out of band,readeither returns the same attrs (orUnowned(attrs), which sync strips before comparison), and a normal deploy/sync with unchanged properties never callsreconcile; thereforesyncAmpTagscannot repair the drift and ownership markers may remain broken indefinitely. The observed tag set needs to participate in drift detection. [ Exceeded comment limit ]alchemy::stack,alchemy::stage, and the bare logicalid. Alchemy permits the same logical ID in different namespaces (the resource identity is the FQN), so twoWorkspace("Metrics", ...)resources in one stack/stage can receive identical internal tags. If state for either resource is lost,findByInternalTagspicks the first match and this provider can attach the wrong workspace to the resource, then update its alias/tags/config and persist the wrong ID while orphaning the original. Recovery needs a namespace/FQN-unique marker or another identity check. [ Exceeded comment limit ]toWireDaysrounds durations to whole days, so validDuration.Inputvalues shorter than 12 hours become0; for example,retentionPeriod: "1 hour"producesretentionPeriodInDays: 0. AMP requiresretentionPeriodInDaysto be at least 1, so reconciliation fails with a validation error even though this property's documentation says it accepts anyDuration.Input. The provider should reject/validate this range before issuing the update (or enforce a minimum conversion). [ Exceeded comment limit ]DescribeWorkspaceConfigurationremainsUPDATING, reaches a failed status, or otherwise never satisfies the predicate beforeSchedule.recurs(30)is exhausted,Effect.repeatreturns the last configuration and this function still succeeds. The reconcile then reports the workspace updated even though the requested retention/limits were not applied; because those fields are not in the returned attributes, a later unchanged deploy may not retry it. [ Exceeded comment limit ]waitActiverepeats until the status is exactlyACTIVEand does not stop for terminal failure statuses. If AMP reports a failed creation, the provider keeps issuingDescribeWorkspacecalls for the full 30-recurrence schedule (about a minute) before surfacing the failure, despite the comment promising fail-fast behavior. This unnecessarily delays every deployment that encounters a terminal workspace failure and can be fixed by making the stop predicate include*_FAILED/other terminal states. [ Exceeded comment limit ]desiredTagsis built as{ ...internalTags, ...news.tags }, so a user-supplied tag can overwritealchemy::stack,alchemy::stage, oralchemy::id. For example, settingtags: { "alchemy::id": "other" }causes the newly created workspace to failhasAlchemyTagson the next read; normal planning then treats the resource as unowned and the tag-based recovery path can no longer find it, leaving the workspace orphaned/unmanageable. User tags must not be allowed to override ownership tags. [ Exceeded comment limit ]CREATING(for example, after a crash aftercreateWorkspacebut before state persistence), butreconcileonly callswaitActivefor a newly created workspace. WhenfindByInternalTagsfinds that in-progress workspace, theworkspace === undefinedbranch is skipped, so alias/tag/config work proceeds andtoAttrscan persist/emit aCREATINGworkspace as successfully reconciled. This defeats the recovery path's readiness guarantee and lets dependents proceed with an unsettled workspace; recovered workspaces should be passed throughwaitActivebefore synchronization/return. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AccessAnalyzer/Analyzer.ts — 0 comments posted, 4 evaluated, 4 filtered
createPhysicalName({ id, maxLength: 255 }), whose prefix starts withstack.name. Stack names are not constrained to begin with a letter, while Access Analyzer requires every analyzer name to match[A-Za-z][A-Za-z0-9_.-]*. A valid stack such as123-prodtherefore produces a generated analyzer name beginning with1, and every deployment of an analyzer without an explicitanalyzerNamefails with AWSValidationExceptionrather than creating the resource. [ Exceeded comment limit ]unusedAccessAgeis a declared create-only managed property, butreaddropsanalyzer.configuration.unusedAccess.unusedAccessAgefrom the returned attributes (andlistdoes the same).synccompares the returned attributes with the persisted attributes and only callsreconcilewhen they differ, so an out-of-band tracking-period change is invisible and will never trigger the replacement needed to restore the configured age. ThegetAnalyzerresult already contains the configuration; the provider needs to expose it inAttributes/read (or otherwise explicitly detect drift). [ Exceeded comment limit ]typereturns a replacement withoutdeleteFirst, butcreateNameis independent oftype, so the replacement uses the same analyzer name as the old generation. Access Analyzer addresses analyzers by name (GetAnalyzerhas no type discriminator), so the create-first replacement collides; theConflictExceptionhandler then reuses the old analyzer, and the replacement flow later garbage-collects that old generation. A type change can therefore finish with the old type deleted and no correctly typed analyzer (or silently retain the old type). This replacement must delete first (and wait for deletion) when the name is unchanged. [ Failed validation ]ConflictExceptionhandler treats every create conflict as a safe concurrent create and then adopts whateverobserve(name)returns. If a foreign actor creates an analyzer with this name after the initial observe (or the name is already occupied in a race), this path proceeds totagResource/untagResourcewithdesiredTags, removing the foreign tags and adding this stack's ownership tags, then records the foreign analyzer as its own. The conflict recovery must verify ownership (or otherwise distinguish the provider's own raced create) before mutating the analyzer. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/Amplify/App.ts — 0 comments posted, 6 evaluated, 6 filtered
syncTagsperformstagResource/untagResourcedirectly, outside theamplifyWriteRetryScheduleused for the other Amplify mutations. When the account-wide Amplify quota returns its documentedBadRequestExceptionwith messageRate exceeded, a tag change fails the reconcile immediately (and can leave the precedingupdateAppapplied), rather than retrying as intended. Wrap both tag mutations in the same bounded retry policy. [ Exceeded comment limit ]syncTagsadds/upserts tags before removing obsolete keys. When an existing app already has Amplify's 50-tag limit and the desired set replaces one key with another (still 50 total),tagResourcetemporarily attempts 51 tags and is rejected beforeuntagResourceruns. Remove obsolete keys first, or update replacements in a way that never exceeds the service limit. [ Exceeded comment limit ]readimmediately returnsundefinedwhenoutput.appIdis absent instead of using the suppliedolds/deterministic identity to recover the app. After an interrupted create has persistedcreatingstate with noattr, a later reconcile receivesolds: undefined; if the app previously had an explicit name that is now changed,findOwnAppsearches only the new name and creates a second app, leaving the successfully-created old app orphaned. Recover the prior app by its persisted identity/tags before creating. [ Exceeded comment limit ]desiredTagsletsnews.tagsoverwrite the reservedalchemy::stack,alchemy::stage, oralchemy::idownership tags. For example, passing{ tags: { "alchemy::id": "other" } }creates/retags the app with the wrong owner, so the nextreadreturnsUnownedand the resource becomes unmanageable (or deployment fails unless--adoptis used). Merge user tags before the internal tags or reject reserved keys. [ Exceeded comment limit ]readreturnsundefinedwithoutoutput.appId(line 232), soreconcilereachesfindOwnApp. If it finds an owned app, this branch simply returns it and skipsupdateAppandsyncTags; a redeploy after state loss with changed description, platform, build settings, or tags reports the desired attributes as applied while the cloud app remains on the old configuration. Apply the desired settings/tags to an app found by this recovery path before returning. [ Exceeded comment limit ]desiredTagstoCreateAppwithout reserving space for its three internal tags. AWS Amplify limits an app's tag map to 50 entries, so a validAppProps.tagsmap with 48–50 user tags becomes 51–53 tags after merging andCreateApp/tag synchronization fails with a validation error. Reject or trim user tags to the available budget before adding internal tags. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/Amplify/Branch.ts — 0 comments posted, 6 evaluated, 6 filtered
readbuildsattrswith onlyappId,branchName,branchArn, andtags, omitting every mutable branch setting such asdescription,stage,enableAutoBuild, environment variables, auth, andttl. The engine'ssynccompares this read result with the stored attributes to detect cloud drift, so an operator changing any of those settings out-of-band produces no detected drift and a subsequent sync leaves the branch incorrect. [ Exceeded comment limit ]news.tagsis spread after the required Alchemy ownership tags, so a user can supplyalchemy::stack,alchemy::stage, oralchemy::idand overwrite them. The branch is then created with tags that failhasAlchemyTagson the next read, causing the provider to report it asUnownedand fail normal subsequent deploys (or repeatedly treat it as an adoption), even though the resource was just created by this stack. [ Exceeded comment limit ]ttlaccepts the fullDuration.Inputtype, including the valid ` [ Out of scope (triage) ]ttlaccepts the fullDuration.Inputtype, including the valid"Infinity"and"-Infinity"inputs supported by Effect.String(toWireSeconds(news.ttl))sends"Infinity"or"-Infinity", but AWS Amplify's branch TTL field accepts only digit strings, so creating or updating a branch with those supported duration inputs fails with a validation error. [ Out of scope (post-validation triage) ]outputis undefined, so the engine's coldreadreturnsundefinedwithout checking the requested name and proceeds toreconcile. Thereconcilepath then observes any existing branch withnews.appId/branchNameand treats it as owned, callingupdateBranchand later overwriting its tags. Thus a pre-existing branch created outside this stack (including one with different Alchemy tags) is silently adopted and mutated instead of being brandedUnownedand blocked unless adoption is explicit. [ Exceeded comment limit ]outputis missing butobservefinds an existing branch, the create/recovery branch returnsexistingdirectly and skips bothupdateBranchandsyncTags. This occurs after state loss or an interrupted prior deployment, and also when a branch already exists with the same identity but stale settings; the provider then persistsdesiredTagsas if applied even though the cloud branch may still have old settings/tags, leaving silent drift. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/ApiGatewayV2/Stage.ts — 0 comments posted, 4 evaluated, 4 filtered
ENSUREblock claims to tolerate a concurrentConflictExceptionand fall through to observation, butretryOnTooManyRequestsonly retries that error and then re-fails it; there is nocatchTag("ConflictException", ...)or subsequentgetStageSafe. If another deploy creates the same stage during this operation, reconciliation fails rather than adopting the existing stage. [ Exceeded comment limit ]createStageis executed at line 341 but its response is never assigned back toobserved. The subsequentsnapshotFromStage(apiId, observed, urls)dereferencesstage.StageName, so every first-time stage creation reaches this path withobserved === undefinedand crashes instead of returning the created stage state. [ Skipped comment generation ]descriptionis treated as managed even when it is omitted:snapshot.description !== news.descriptionmakes drift true for any existing stage with a description, whileupdateStagesendsDescription: undefined(which is omitted from the AWS request), so the description is never changed. The same non-converging behavior occurs forclientCertificateIdat line 386. Such stages are updated on every reconcile without reaching the declared partial configuration. [ Exceeded comment limit ]syncTagspassesstageArn(...), whose helper constructs anarn:aws:...ARN. AWS uses partition-specific prefixes such asarn:aws-us-govin GovCloud (andarn:aws-cnin China), so reconciliation of a stage with tag drift in those regions sends a resource ARN for the wrong partition and tag/untag operations fail instead of converging. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppConfig/ConfigurationProfile.ts — 0 comments posted, 10 evaluated, 10 filtered
configurationProfileNameis omitted,toNamederives the lookup name from the ambientInstanceIdviacreatePhysicalName. On a cold-start read the planner supplies a newly generated adoption instance ID, so the lookup name differs from the name created by the previous instance. A state-loss/import recovery therefore fails to find the existing profile and creates another one, orphaning the original profile (and any hosted versions under it). [ Exceeded comment limit ]configurationProfileArn, which hard-codes theawspartition. Inaws-cnoraws-us-gov, the correct ARN partition isaws-cnoraws-us-gov; the resulting ARN is therefore invalid forreadAppConfigTags/syncAppConfigTagsand is also persisted as the profile output ARN. Profile creation and subsequent ownership checks fail or produce unusable attributes in those partitions. [ Exceeded comment limit ]readfinds a tagged profile but does not return itsType, andreconcileuseslocationUrionly on create whileupdateConfigurationProfilecannot change either immutable field. When an existing tagged profile is adopted with a different desiredlocationUriortype, the forced adoption update leaves the physical profile unchanged, while state is recorded from the desired props, so later plans report no replacement or drift. [ Exceeded comment limit ]desiredTagslets user tags overwrite the ownership markers becausenews.tagsis spread afterinternalTags. A valid tag such as{ "alchemy::id": "custom" }causes the newly created profile to failhasAlchemyTags(id, tags)on the next read, so the provider treats its own resource asUnownedand normal subsequent deploys fail or require adoption. Reserved internal keys should win or be rejected. [ Exceeded comment limit ]reconciletreats any profile returned byfindByNameas owned without checking its Alchemy tags. The engine's earlier ownership probe is not atomic with apply: if another stack creates a same-name profile after planning (or replaces a missing output before retry), this fallback finds that foreign profile, callsupdateConfigurationProfile, and then rewrites its tags. This bypasses theUnowned/adoption guard and can mutate another stack's configuration and ownership metadata. [ Exceeded comment limit ]locationUriortypeis marked as a replacement, but the replacement is notdeleteFirstand uses the same profile name under the same application. During the create-first replacement,outputis empty while the old profile still exists, sofindByName(applicationId, name)returns that old profile; the provider then updates it without changing either immutable field and records it as the new generation. The requested source/type change is therefore silently ignored. [ Exceeded comment limit ]Description: news.descriptioneven when the desired optional description has been removed. The AWS API treatsDescriptionas an optional PATCH field, and the SDK omits anundefinedvalue, so the old description remains in the profile instead of being cleared (an empty string is the valid zero-length value). Subsequent deploys continue to report success while the cloud profile drifts from the declared props. [ Exceeded comment limit ]validatorsfrom the desired profile is also not convergent:toWireValidators(undefined)yieldsundefined, so the SDK omitsValidatorsfrom the PATCH and AWS retains the existing validators. Because the provider does not return or diff validator state, the stale validation rules remain silently after a successful deployment; an explicitly empty list is needed to clear them. [ Exceeded comment limit ]KmsKeyIdentifieris handled the same way: removingkmsKeyIdentifierfrom the desired props sendsundefined, which is omitted from the PATCH request, so an existing hosted profile keeps using its previous KMS key. The API permits an empty identifier when updating, but this provider never sends that clear value and does not expose the drift in its returned attributes. [ Exceeded comment limit ]listHostedConfigurationVersionscompletes, it is not deleted anddeleteConfigurationProfilerejects because a hosted version remains, so destroy leaves the profile behind. Re-list/retry the cleanup or otherwise coordinate writers before the final delete. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppConfig/Environment.ts — 0 comments posted, 3 evaluated, 3 filtered
reconciledeliberately choosesoutput.environmentNameasname, so the observed drifted name is reused rather than the desiredtoName(id, news)value. Becausediffcompares only the desired props, a normal deploy with unchanged props will not notice it;alchemy syncwill detect the changedenvironmentNamebut call this reconcile and persist the wrong name without restoring it. AWSUpdateEnvironmentsupportsName, so this is a reachable drift case. [ Exceeded comment limit ]desiredTagslets user-suppliednews.tagsoverwrite the reservedalchemy::stack,alchemy::stage, oralchemy::idownership markers. For example,tags: { "alchemy::id": "other" }creates/syncs an environment whose ownership check atreadfails, so a later cold-start/adoption refuses it asUnownedandsynccannot repair tags when attributes are otherwise unchanged. User tags should not be able to replace the internal ownership values. [ Exceeded comment limit ]deletecallsDeleteEnvironmentwithout settingDeletionProtectionCheck: "BYPASS". When the account has AppConfig deletion protection enabled and the environment was recently read byGetLatestConfiguration/GetConfiguration, AWS rejects this request withBadRequestException; the environment then cannot be destroyed (and the same path is used byunsafe nuke) until the protection window expires or an operator deletes it manually. The provider exposes no property that lets callers choose the check behavior. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppConfig/Extension.ts — 0 comments posted, 4 evaluated, 4 filtered
desiredTagslets user tags override the reserved ownership tags because{ ...internalTags, ...news.tags }spreadsnews.tagslast. A caller supplying anyalchemy::stack,alchemy::stage, oralchemy::idtag creates an extension whose ownership metadata is wrong; subsequenthasAlchemyTagschecks return false, so the next deploy treats this stack-owned extension asUnownedand fails or requires adoption, while tag sync preserves the bad values. [ Exceeded comment limit ]output.extensionIdis absent,reconciletreats any extension returned byfindByName(name)as owned and proceeds to update it and sync tags. This bypasses theread/Unownedownership check for unresolved-input deployments (the planner skips its adoption probe whileextensionNameis unresolved), and also for a create-vs-create name race. An existing foreign extension with that eventual name can therefore be silently overwritten, and later managed/deleted, withoutadoptbeing enabled. [ Exceeded comment limit ]updateExtensioncreates a new extension version, but this reconcile path only returns the new version and never updates existingExtensionAssociationresources. AppConfig materializes the association'sExtensionVersionNumberwhen the association is created, so associations that omitted a version remain bound to the old version and continue invoking the old actions/parameters after an extension update. [ Exceeded comment limit ]deleteExtensionis called withoutVersionNumber, but AppConfig deletes only the highest extension version when that query parameter is omitted. After anyupdateExtensionhas created version 2 (and so on), destroy removes only the newest version and leaves older versions and the extension resource behind, causing leaked cloud resources and name conflicts on later deployments. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppConfig/ExtensionAssociation.ts — 0 comments posted, 4 evaluated, 4 filtered
findByIdentifiersresolves an extension ID but never checks the requestedextensionVersionNumberagainst the matched association's versionedExtensionArn. On a cold-start adoption, an existing same-extension/resource association at an older version is therefore accepted as the desired resource; the planner adopts it and reconcile only updates parameters/tags, leaving the requested pinned version unapplied. [ Exceeded comment limit ]findByIdentifiersusesitem.ExtensionArn.includes(extension.Id!)as the identity test. Because the ARN contains the extension ID as one path segment, substring matching can select a different extension whose ID is a prefix of the requested ID (or whose ID merely appears elsewhere in the ARN). The provider can then update parameters/tags or return attributes for the wrong association, corrupting another extension's configuration and state. [ Exceeded comment limit ]news.tagsand sends the result to AppConfig. AppConfig permits at most 50 tags, so a valid user configuration containing 50 user tags is expanded to 53 andCreateExtensionAssociation/tag sync fails validation instead of creating the resource. The provider needs to reserve/validate capacity for its internal tags. [ Exceeded comment limit ]extensionVersionNumberis declared replacement-only, but the provider does not make that replacement delete-first. A replacement create for the same extension/resource pair cannot create a second association; it fails (or, after recovery,findByIdentifiersrediscovers the old pair) rather than producing the requested version. Consequently version changes leave the old association in place or fail the deployment instead of converging to the new extension version. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppIntegrations/Application.ts — 0 comments posted, 2 evaluated, 2 filtered
approvedOriginsarray is forwarded asApprovedOrigins: []indesiredSourceConfig. The AppIntegrationsApplicationApprovedOriginsshape has a minimum of one member, so creating or updating withapprovedOrigins: []is rejected by AWS rather than clearing the list; this is especially exposed by the update comparison, which deliberately treats[]as a desired empty state and sends it. Either reject this prop locally or represent removal using the API's supported omission semantics. [ Exceeded comment limit ]reconcilefalls back tofindByNamespace(news.namespace)and treats any matching application as the replacement resource, without checkinghasAlchemyTags/Unowned. This is reachable when an existing managed resource changes to a namespace already occupied by an unrelated application: the engine creates the replacement directly (without the cold-start adoption probe), this lookup finds the foreign app, and the subsequent update/tag/return path mutates and takes ownership of it. A namespace collision should be rejected unless ownership is verified. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppIntegrations/DataIntegration.ts — 0 comments posted, 3 evaluated, 3 filtered
Applicationprovider documents this exact behavior and instead recovers by its stable namespace). With no cachedoutput,read/reconcilecompute a new name andfindByNamecannot find the existing integration, so reconciliation creates another integration and leaves the old one orphaned (potentially causing duplicate ingestion). Recovery must use a stable identity or enumerate/verify ownership rather than the generated name. [ Exceeded comment limit ]DuplicateResourceExceptionrace. Two deploys (or a retry after the first call succeeded but state persistence failed) can both observe no resource and callcreateDataIntegration; the loser receivesDuplicateResourceExceptionand the reconciliation fails even though the integration now exists. The other AppIntegrations providers re-enumerate by identity when this race occurs, so this provider should do the same. [ Exceeded comment limit ]deleteDataIntegrationis called withDataIntegrationIdentifier, but the AppIntegrations DeleteDataIntegration API requires the request fieldIdentifier(the same field used bygetDataIntegrationandupdateDataIntegrationabove). Consequently every destroy attempt sends no identifier and fails with an invalid request instead of deleting the integration, leaving the cloud resource behind. [ Failed validation ].repos/alchemy-effect/packages/alchemy/src/AWS/AppIntegrations/EventIntegration.ts — 0 comments posted, 2 evaluated, 2 filtered
sourceoreventBridgeBusreturnsreplace, but replacement reconciliation still observes by the unchangednameat lines 200–206. Because the old integration must remain during the default create-first replacement and names are unique,observe(name)finds the old object, so no new immutable configuration is created; subsequent garbage collection can then delete that same object, leaving the stack with no integration. [ Exceeded comment limit ]diffTagsat line 251, whosefor...in/inchecks treat inherited names such astoStringas already present. AWS tag keys allow these names, so removing a user tag namedtoString(or similar prototype key) is never placed inremovedand the stale cloud tag remains indefinitely. [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy/src/AWS/AppRegistry/ResourceAssociation.ts — 0 comments posted, 3 evaluated, 3 filtered
readomits the association'soptionsfrom the returned attributes, so drift inAPPLY_APPLICATION_TAGversusSKIP_APPLICATION_TAGis invisible tosync: when the association still exists, the returned object can equal persistedold.attr, causingSync.tsto classify it asunchangedand never callreconcile. Thus an out-of-band option/tag change is not repaired despitereconcileclaiming to synchronize options. [ Exceeded comment limit ]associateeffect swallows everyConflictException, and the final read only checks thatobserved.resource.arnexists; it never verifiesobserved.optionsagainstnews.options. If the initial read races with another association, or the recreate call races while the old association is still present, the conflict is treated as success and the provider returns attributes for an association that may still have the old/different tag option. A later apply can therefore report success without converging the requested option. [ Exceeded comment limit ]news.optionsis later removed, this branch skips synchronization entirely because it only enters the recreate path whennews.options !== undefined. Since omitting options onassociateResourceuses the documented service defaultAPPLY_APPLICATION_TAG, removingoptionsshould restore that default, but the oldSKIP_APPLICATION_TAGassociation remains unchanged and the provider reports success. Reconcile the omitted case against the default option as well. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppRunner/AutoScalingConfiguration.ts — 0 comments posted, 2 evaluated, 2 filtered
desiredTagslets user-suppliednews.tagsoverwrite the ownership markers produced bycreateInternalTags. A tag set such as{ "alchemy::id": "other" }is written during creation, but the nextreadchecks the real logical ID and returnsUnowned, causing later deploys to fail ownership checks (or require an unsafe adopt) for a resource this stack created. [ Exceeded comment limit ]news,havePropsChangedschedules an update, butdriftedonly compares fields whose new value is non-undefined. For example, changingmaxConcurrencyfrom50to omitted leaves the existing revision at50instead of creating a revision with the documented default, so the cloud configuration no longer matches the desired props and subsequent deployments silently keep the stale value. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppRunner/ObservabilityConfiguration.ts — 0 comments posted, 2 evaluated, 2 filtered
desiredTagsletsnews.tagsoverwrite the reservedalchemy::stack,alchemy::stage, andalchemy::idownership tags. A user who supplies any of those keys creates a configuration thatreadimmediately classifies asUnowned, so subsequent deploys fail unless adoption is forced; with a matching name/id, spoofed tags can also make another stack treat the configuration as its own. Merge user tags first and internal tags last (or reject reserved keys). [ Exceeded comment limit ]traceConfigurationfrom an existing resource never disables tracing. When the active revision has a vendor andnews.traceConfigurationisundefined, the condition atdriftedis false, so reconciliation skipscreateObservabilityConfigurationand returns the existing X-Ray revision unchanged. This contradicts the prop contract that omitting the configuration means tracing is disabled and leaves the resource permanently out of sync after a vendor-to-omitted update. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/AppRunner/Service.ts — 0 comments posted, 13 evaluated, 13 filtered
ensureRepositorytreats anyRepositoryAlreadyExistsExceptionas an owned repository: it describesrepositoryNameand never checks for the stack/id ownership tags before returning its URI. A pre-existing repository with the deterministic name is therefore used for image pushes, and the resulting state causesdeleteto later calldeleteRepository(..., force: true)on it. A name collision or leftover foreign repository can consequently be overwritten and permanently deleted by destroying this service. [ Exceeded comment limit ]attachBindingsstrips all non-alphanumeric characters from each policy statement'sSidbut does not ensure the resulting IDs remain unique. Two valid binding statements such asAllowFoo-BarandAllowFooBar(or two bindings with the same Sid) become the same Sid in the generated inline policy; IAM requires Sid values to be unique within a policy, soputRolePolicyrejects the deployment whenever such bindings are combined. [ Exceeded comment limit ]exports.program, andcreateHostRuntimeContextconstructs that from registeredhost.run/serverunners. For an otherwise valid Effect-native implementation that returns{}(thefetchfield is optional and nohost.runis registered),Effect.all([])completes immediately, soEffect.runPromise(program)exits and App Runner repeatedly restarts an image with no listening server. The provider should reject a service with no runner or keep the process alive. [ Exceeded comment limit ]index.mjsand*.js, even thoughBundleOutput.filesexplicitly includesrolldownassets (for example.css,.map, or plugin-emitted/static files) andmaterializewrites every emitted file into the context. A native service whose bundle references such an emitted asset will build successfully but the asset is absent from the image, causing runtime module/file loads to fail. [ Exceeded comment limit ]COPY *.js /app/, but the bundle can legitimately contain only the renamedindex.mjsand no.jschunks (the comment immediately above even says minimal bundles emit none). Docker treats an unmatchedCOPYglob as an error (no source files were specified), so deploying a minimal Effect-native App Runner service fails during image build instead of producing an image. Emit this instruction only when chunks exist or copy a directory/pattern that is guaranteed to match. [ Exceeded comment limit ]waitUntilGoneconvertsDELETE_FAILEDinto a plainError, while its retry predicate only retriesAppRunnerServiceNotSettled; therefore a service entering AWS'sDELETE_FAILEDstate immediately aborts deletion. Because ECR and IAM cleanup occurs only afterwaitUntilGone,stack.destroy()leaves the managed repository and roles behind, even though App Runner documents retryingDeleteServicefor this status. [ Exceeded comment limit ]readbuilds attributes only fromtoAttrs(service, platformAttributesOf(output)); it never records or comparesSourceConfiguration, instance, health, network, observability, or scaling settings. Sincesyncdecides whether to callreconcileby deep-comparing these attributes, an out-of-band image/configuration change that leaves the service's IDs/status unchanged appears unchanged forever andsourceDrifted/the other drift checks are never reached. The provider therefore does not actually self-heal the drift its reconcile logic claims to handle. [ Exceeded comment limit ]desiredTagslets user-suppliednews.tagsoverwrite the ownership markers fromcreateInternalTagsbecause the spread order is{ ...internalTags, ...news.tags }. Supplying anyalchemy::stack,alchemy::stage, oralchemy::idtag makes the newly created service failhasAlchemyTagson the next read and be treated asUnowned(and can spoof another resource's identity), causing later deploys to reject or take over the service and undermining ownership protection. [ Exceeded comment limit ]news.main !== undefinedand silently ignores a simultaneously suppliednews.imageRepository. The publicServicePropscontract documents these inputs as mutually exclusive, so a configuration containing both unexpectedly builds/pushes a managed image and deploys it instead of rejecting the invalid combination; the caller's low-level image is not the one used. [ Exceeded comment limit ]outputas proof that managed dependencies still exist: it skipsensureRolewheneveroutput.instanceRoleArn/accessRoleArnis present and skipsensureRepositorywheneveroutput.repositoryUrimatches the name. If an operator deletes one of those IAM roles or the ECR repository out of band,readServicestill succeeds but this path reuses the stale ARN/URI; policy attachment or image push then fails and the provider never recreates the missing dependency, leaving the service unrecoverable through a normal deploy. [ Exceeded comment limit ]news.envvalues override the runtime-critical variables because the merge puts...news.envafter...alchemyEnv. In particular, settingenv.PORTto a value different fromnews.portmakes the generatedBunHttpServerread the wrongPORTand bind there while App Runner routes/health-checks the configured port, leaving the service unhealthy; overridingALCHEMY_STACK_NAME/ALCHEMY_STAGElikewise gives the runtime an incorrect stack identity. Reserve these keys or apply framework values last. [ Failed validation ]mainto the low-levelimageRepositoryform is not markedreplacebydiff, and the low-level reconcile returnstoAttrs(observed, emptyPlatformAttributes). That overwrites the saved managed ECR/role names withundefined; the old managed repository and IAM roles remain in AWS, but a laterdeletehas no names to clean them up. Either reject/formally replace this transition or retain and reap the old platform attributes. [ Exceeded comment limit ]list()returns every App Runner service withemptyPlatformAttributes, including Effect-native services whose ECR repository and IAM role names are required bydelete. Account-widealchemy unsafe nukepasses these listed attributes directly todelete, so it removes the service but cannot enter the repository/role cleanup branches; every native service discovered by nuke leaks its managed ECR repository and both IAM roles. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/Cognito/User.ts — 0 comments posted, 2 evaluated, 2 filtered
usernameAttributes.createNamealways produces a physical name whenprops.usernameis omitted, but this resource's own contract says such pools require the username to be a value of an attribute (for example an email address). AUserwithuserPoolIdpointing to an email/phone-username pool and no explicitusernametherefore sends an invalid generated identifier toAdminCreateUserand cannot be created. [ Exceeded comment limit ]reconcilecontains the attribute andEnabledsynchronization, butdiffonly checks the username and pool identity;attributesOfalso omitsenabledand the managed attributes from the output. After someone changes a user attribute or disables/enables the user in Cognito, a normal redeploy with identical props plans a noop and never invokesreconcile, leaving the user permanently divergent unless the user props change or a forced run is used. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/EC2/KeyPair.ts — 0 comments posted, 4 evaluated, 4 filtered
difftreatskeyTypeandkeyFormatchanges as replacement triggers even whenpublicKeyMaterialis supplied, although both props are documented as ignored for imported keys. For example, changing onlykeyFormaton an imported key returns{ action: "replace" }, causing Alchemy to delete and re-import an otherwise unchanged key and potentially interrupt SSH consumers. [ Exceeded comment limit ]listcallsec2.describeKeyPairs({})only once instead of consuming its paginated results. In accounts with more than one response page, branded key pairs on later pages are omitted from the provider's enumeration, so account-wide operations such asalchemy unsafe nukecannot discover and delete them and the provider's required exhaustive listing is violated. UsedescribeKeyPairs.pages({})and flatten every page. [ Failed validation ]InvalidKeyPair.Duplicateintoundefinedand then blindly describe the name, after which reconcile updates tags and returns the key. If the initial describe raced with another stack/actor creating that name (or two resources use the same explicitkeyName), this provider adopts the other key without checking its Alchemy ownership tags; the stacks then share onekeyPairId, and destroying either can delete the other's SSH key while tag sync also overwrites its ownership metadata. [ Exceeded comment limit ]info.KeyTypeto"rsa"at line 243, even thoughpublicKeyMaterialcan be an OpenSSH/Ed25519 key and the props documentation says imported keys retain their own algorithm. A first deployment of an Ed25519 imported key therefore returns and persistskeyType: "rsa"until a later read happens to recover the cloud value, giving consumers incorrect key metadata. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/ECS/Service.ts — 0 comments posted, 1 evaluated, 1 filtered
vpcIdandsubnetsindependently optional permits callers to provide only one, but the provider resolves any partial pair against the account's default VPC/network instead of resolving the missing side within the explicitly supplied VPC. For example,vpcId: myVpcwith nosubnetsuses default-VPC subnets, andsubnets: myVpcSubnetswith novpcIdcomposes target groups/security groups in the default VPC; these mismatches can make service or managed-ingress deployment fail (or attach resources to the wrong VPC). [ Failed validation ].repos/alchemy-effect/packages/alchemy/src/AWS/ELBv2/ListenerRule.ts — 0 comments posted, 4 evaluated, 4 filtered
readreturns onlyruleArn,listenerArn,priority, andisDefault, whilereconcilemanages conditions, actions, and tags. The engine'ssynccompares the attributes returned byread; consequently, an out-of-band change to a rule's conditions, actions, or tags produces an identical attribute snapshot and never invokesreconcile, so drift is silently left in production. The observed attributes/read path must include the managed rule configuration (or otherwise make those fields part of drift detection). [ Exceeded comment limit ]reconcileforwardsnews.actionstoserializeActionswithout enforcing the required ordering. The serializer assignsOrderfrom the caller's array order, so a validListenerAction[]such as[forward, authenticateOidc]produces the terminal action first. AWS requires exactly one routing action (forward,redirect, orfixed-response) and requires it to be last, so creating or updating such a rule fails instead of normalizing the actions or rejecting the input clearly. [ Exceeded comment limit ]readreturnsundefinedwheneveroutputlacksruleArn, andreconcilethen calls non-idempotentcreateRulewithout looking up the existing rule bylistenerArnandpriority. If AWS created the rule but the process crashed before persisting its attributes, the next deploy retriescreateRule, receivesPriorityInUse, and leaves the already-created rule orphaned/unmanageable. Use a natural-key lookup (or another idempotent recovery mechanism) before creating. [ Exceeded comment limit ]setRulePriorities. A valid reordering that swaps two priorities (for example rule A10 -> 20and rule B20 -> 10) cannot converge: the first call sees the other rule still occupying its target and returnsPriorityInUse, so the second rule is never updated. AWS supports submitting multiple rule-priority pairs together; the provider needs a coordinated/batched or temporary-priority strategy for swaps. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/ELBv2/TrustStore.ts — 0 comments posted, 3 evaluated, 3 filtered
outputis absent,reconcilesearches byNames: [name]and treats any matching trust store as its own without checking the internal ownership tags (or any other ownership marker). A first deployment with an explicitnamethat already belongs to another stack/operator therefore callsmodifyTrustStoreon that object, rewrites its CA bundle, and later rewrites/removes its tags instead of failing the name collision. [ Exceeded comment limit ]until: (ts) => ts?.Status === "ACTIVE"does not verify that the predicate ever became true.Effect.repeatreturns the last successful description when itstimesbudget is exhausted, so a trust store that is stillCREATINGafter the ten polls continues through tag sync and is returned as a successful resource with a non-ready status; downstream listeners can then be reconciled against an unusable trust store instead of the deployment retrying/failing. [ Exceeded comment limit ]deletecatchesTrustStoreInUseExceptionafter the bounded retry schedule and converts it to success. If a listener is still attached (or detachment takes longer than the retry window), the trust store remains in AWS while Alchemy records the resource as deleted and drops its output, leaving an orphaned in-use trust store that future destroys will no longer target. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/FraudDetector/DetectorVersion.ts — 0 comments posted, 2 evaluated, 2 filtered
expression,outcomes,language, or description with the sameruleIdis detected as a detector-version replacement, butensureRulesreuses the latest existing rule version instead of creating the requested rule version. Because replacements are create-first and the new reconcile receivesolds: undefined, the replacement detector version continues referencing the old rule and the desired fraud logic is silently never deployed; subsequent runs see the new props and stop trying. [ Exceeded comment limit ]ensureRulesreuses any pre-existing rule version for a matchingruleId, butdeletelater unconditionally callsdeleteRulefor every rule returned by the detector version. Deleting an Alchemy version can therefore delete a rule that was created by another stack/user (and is merely referenced here), destroying shared fraud-detection configuration; shared references may instead make the delete fail and leave cleanup incomplete. Track ownership/creation before deleting rules, or do not delete reused rules. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/GlobalAccelerator/Accelerator.ts — 0 comments posted, 6 evaluated, 6 filtered
ipAddressesis declared stable across every accelerator update, butreconcileallowsipAddressTypeto change betweenIPV4andDUAL_STACK. The resource docs say dual-stack assigns both IPv4 and IPv6 addresses, andtoAttributesflattens every returnedIpSetsentry intoipAddresses, so that update can change this output. The engine will preserve the old stable output for downstream consumers, leaving them with stale addresses after a valid IP-type update. [ Exceeded comment limit ]listmapsListAcceleratorsresults directly throughtoAttributeswithout fetchingdescribeAcceleratorAttributes, so every listed accelerator is reported withflowLogsEnabled: falseand no flow-log bucket/prefix even when flow logs are configured. This violates the provider's full-attributes list contract and gives callers an incorrect inventory snapshot (the current nuke path happens not to use those fields). [ Out of scope (post-validation triage) ]readfetches tags only to decide whether to brand the sameattrsasUnowned; tags are not included inAcceleratorattributes.syncstrips that brand and deep-compares the tag-less attributes, so removing or changing an accelerator tag is invisible and never calls the tag-repair logic inreconcile. The provider therefore does not converge tag drift unless some unrelated accelerator field also changes. [ Exceeded comment limit ]ipAddressTypeas defaulting to"IPV4", but reconciliation only updates it whennews.ipAddressType !== undefined. If a deployed accelerator is changed from explicit"DUAL_STACK"to omitting the prop, the generic diff schedules an update, yet this branch skips the update and leaves the accelerator dual-stack indefinitely. Compare againstnews.ipAddressType ?? "IPV4"instead. [ Failed validation ]news.flowLogs.prefixis removed, the drift check deliberately skips prefix comparison because it only checks the prefix when it is defined. An accelerator previously configured with a non-empty prefix therefore keeps writing to the old prefix forever, despite the props documenting an omitted prefix as the root/default location. The reconciler should compare against an explicit default and send a valid value that resets the prefix. [ Exceeded comment limit ]flowLogs.prefixis optional and the documented{ flowLogs: { bucket } }form is allowed, but enabling logs sendsFlowLogsEnabled: truewithFlowLogsS3Prefix: undefined. AWS requires bothFlowLogsS3BucketandFlowLogsS3Prefixwhenever flow logs are enabled, so this request is rejected and any accelerator configured without a prefix can never reconcile. Supply the documented default prefix (or otherwise ensure a valid prefix is sent). [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/GuardDuty/IPSet.ts — 0 comments posted, 2 evaluated, 2 filtered
toNamecallscreatePhysicalNamewithout passing the lifecycleinstanceId, so its generated name uses the newly providedInstanceIdfrom the plan context on each deploy. When an IP set is created successfully but state persistence then fails (or state is later lost), the nextread/reconcilecomputes a different name,findByNamemisses the existing set, andcreateIPSetcreates another active set while leaving the original orphaned. Use the prior instance identity or another stable recovery key when deriving the lookup name. [ Failed validation ]reconcileaccepts any IP set returned byfindByNameand never verifieshasAlchemyTags, even thoughreadbrands an untagged set asUnowned. If a previously managedoutput.ipSetIdis gone and another actor creates an IP set with the same name, an ordinary deploy follows this fallback and updates its location/activation and rewrites its tags, bypassing the engine'sadoptguard (existing-state plans do not rerunread). This can silently take over and mutate a foreign security list. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/KMS/Key.ts — 0 comments posted, 2 evaluated, 2 filtered
output === undefinedpath always callscreateKeyand never searches for an existing key carrying the resource's internal tags. IfcreateKeysucceeds but the process crashes or state persistence fails before the output is written (a lifecycle case the provider contract explicitly requires providers to handle), the next reconcile creates another customer KMS key; the first key is orphaned and cannot be found or deleted by this provider. [ Exceeded comment limit ]createKey, the provider immediately callsreadKey, whoseDescribeKey/tag/policy/rotation reads are not retried as a post-create consistency window. AWS documents KMS as eventually consistent and permits transientNotFoundException/InvalidStateExceptionafter changes;readKeyconvertsNotFoundExceptiontoundefined, so a transient read can make this branch die at!stateeven though the key exists, leaving an untracked customer key (and the next retry creates another because there is no tag-based recovery). [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/KinesisVideo/SignalingChannel.ts — 0 comments posted, 2 evaluated, 2 filtered
ResourceInUseExceptionrecovery aftercreateSignalingChanneltreats any channel that appeared after the initial observe as the stack's resource: it waits forACTIVEand proceeds to TTL/tag synchronization without re-checkinghasAlchemyTags. If another actor creates the same named channel in that race window, this provider mutates that unowned channel (including overwriting/removing tags), causing cross-stack resource corruption. [ Exceeded comment limit ]createSignalingChannelkeeps returningResourceInUseExceptionbecause a previous incarnation is stillDELETING, the retry is exhausted and the error is converted to success at line 228. The code then only callswaitForChannelActiveand never retries creation; once the deleting channel is purged, the name remains absent and the bounded wait fails withKinesisVideoNotConverged, so a normal replacement can fail instead of creating the channel. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/LakeFormation/DataCellsFilter.ts — 0 comments posted, 1 evaluated, 1 filtered
tableCatalogIdvalues instead of their effective value. A filter deployed with the documented default (omitted, which means the caller account) is markedreplacewhen the user later writes that same account ID explicitly, even though the target catalog has not changed. This unnecessarily deletes and recreates the filter and can briefly disrupt grants that reference it. [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy/src/AWS/LakeFormation/DataLakeSettings.ts — 0 comments posted, 1 evaluated, 1 filtered
listalways returns one settings row anddeleteonly restores managed fields), but it does not setnuke: { singleton: true }.alchemy unsafe nuketherefore discovers the row, invokesdeletewith the un-managed attributes fromlist, gets a successful no-op, and reports the singleton as deleted even though the data-lake settings remain. It should be excluded from nuke like other singleton settings. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/MQ/Configuration.ts — 0 comments posted, 7 evaluated, 7 filtered
engineTypeandengineVersionare documented as immutable but are omitted from the provider'sstableslist. When this configuration has an in-place update (for example adatarevision), the planner exposes onlyconfigurationId,configurationArn, andconfigurationNameto downstream resources; references toconfiguration.engineTypeorconfiguration.engineVersionthen resolve as missing during that plan, potentially sending incomplete values to dependent resources. Include the immutable engine fields as stable attributes. [ Exceeded comment limit ]engineType,engineVersion, andauthenticationStrategydo not setdeleteFirst: true. Those changes retain the sameconfigurationName, so the engine's default create-first replacement callscreateConfigurationwhile the old configuration with that unique name still exists; the create is rejected and the replacement can never complete. Name-changing replacements can remain create-first, but same-name immutable changes need delete-first handling (or another coexistence strategy). [ Failed validation ]authenticationStrategyomitted, Amazon MQ usesSIMPLE, but changing the props from omitted to explicitauthenticationStrategy: "SIMPLE"(or back) comparesundefinedwith"SIMPLE"and forces a replacement. This unnecessarily replaces a live configuration—and can disrupt brokers referencing it—even though the effective setting has not changed. [ Failed validation ]reconcilesyncsdataand tags, butdiffonly checks immutable identity fields andread/toAttrsexpose neither tags nor the revision content. Consequently, an operator changing the latest configuration revision or tags in AWS followed by a redeploy with identical props produces a noop and never runs the sync code. [ Exceeded comment limit ]desiredTagsspreadsinternalTagsfirst andnews.tagssecond. For example,tags: { "alchemy::id": "other" }causes the created configuration to failhasAlchemyTagson the next read, so the resource is treated asUnownedand normal updates/adoption cannot converge (the same collision can also break stack/stage ownership). Reservedalchemy::*keys must be applied after user tags or rejected. [ Exceeded comment limit ]findByName(name)as absent, then onecreateConfigurationsucceeds while the other receives the service's duplicate-name conflict; unlike the neighboring MQ broker provider, this error is not caught and re-observed. The losing reconciliation fails even though the desired configuration now exists, and a failed first apply/retry can leave the stack wedged until another run. [ Exceeded comment limit ]descriptionchanges are never applied when the configuration data is unchanged. A deploy that changes onlynews.descriptionstill reachesreconcilebecause the props changed, but this condition only callsupdateConfigurationwhencurrentData !== news.data; with identical data it skips the API call and then persists the new props, so the old revision description remains in AWS permanently and future deploys no longer retry it. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/RedshiftData/StatementsHttp.ts — 0 comments posted, 2 evaluated, 2 filtered
StatementsHttpalways addsredshift-serverless:GetCredentialsto the host policy, even whenoptions.secretArnselects Secrets Manager authentication. This gives the function an unnecessary alternate credential-minting capability (the comment itself says this permission is used only when noSecretArnis supplied); condition this statement on the IAM-auth path so secret-backed bindings do not receive it. [ Exceeded comment limit ]queryexhausts itstimesbudget while Redshift still reportsSUBMITTED,PICKED, orSTARTED, this code falls through with that nonterminal response and emitsRedshiftStatementFailedwithout callingcancel. The caller sees a failed query even though the SQL can continue running (and may continue consuming capacity or applying writes) after the request has returned; timeout handling should use a distinct timeout error and cancel the statement or otherwise make the orphaned execution explicit. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/SSMContacts/Rotation.ts — 0 comments posted, 3 evaluated, 3 filtered
rotationArnhard-codes theawspartition. In GovCloud and China, AWS SSM Contacts ARNs usearn:aws-us-govorarn:aws-cn; the provider therefore cannot resolve an existing rotation (and, after creating one, the follow-upgetRotation(arn)returnsundefinedbeforerotation.ContactIdsis accessed). Deploys in those partitions fail and can leave the newly created rotation orphaned. [ Exceeded comment limit ]RotationProps.startTimeis optional and AWSCreateRotationpermits omittingStartTime, butbuildAttrsunconditionally callsrotation.StartTime.toISOString(). A rotation created without a start time (or an API response with the optional field absent) throws during the post-create return and on later reads, so reconciliation fails after creating the resource and can leave it orphaned. [ Exceeded comment limit ]ConflictExceptionpath assumes the concurrently existing rotation is safe to manage: after swallowing the conflict, line 228 fetches it and the provider immediately syncs contacts, recurrence, time zone, start time, and tags without recheckinghasAlchemyTags. If another stack/process owns the same explicit name and wins the create race, this provider overwrites that rotation instead of rejecting it, defeating the ownership/adoption safeguard. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/SageMaker/ClusterSchedulerConfig.ts — 0 comments posted, 5 evaluated, 5 filtered
namereturns a normal{ action: "replace" }, which the engine performs create-first by default. SageMaker permits only one scheduler config per cluster, so the old config still occupies the cluster when the replacement create runs and AWS returnsClusterSchedulerConfigAlreadyExists; the name change can never converge. This replacement needs delete-first semantics (at least for same-cluster name changes). [ Exceeded comment limit ]desiredTagsis built as{ ...internalTags, ...news.tags }, allowing user-suppliedtagsto overwritealchemy::stack,alchemy::stage, oralchemy::id. A policy created with those reserved keys no longer carries this stack's ownership marker, so later reads classify its own resource asUnowned(or can attribute it to another stack), causing adoption failures and potentially incorrect ownership-driven lifecycle actions. [ Exceeded comment limit ]output.clusterSchedulerConfigIdis gone, this fallback adopts whichever policy has the deterministicnamewithout checkinghasAlchemyTags. If the old policy was deleted and another actor creates a same-named policy,reconcileupdates its scheduler config and rewrites its tags, even thoughreadwould classify that object asUnowned; this can silently mutate a foreign SageMaker policy. [ Exceeded comment limit ]ConflictException, but SageMaker actually returns the typedClusterSchedulerConfigAlreadyExistserror for the one-policy-per-cluster conflict (the repository's ownClusterSchedulerConfig.test.tsasserts this tag). Therefore a concurrent create, or any existing policy on the cluster, propagates as a failed deployment instead of being re-observed by name as this code intends. [ Failed validation ]reconciletreats any non-undefineddescription as an existing resource, butdescribeConfigOrUndefinedpreservesStatus === "Deleted"(the provider itself handles that status elsewhere). With stale state pointing at a deleted policy, theelsebranch callswaitForConfig(..., "Ready");waitForConfigclassifiesDeletedasSchedulerConfigNotReady, so deployment retries for the full bounded wait and fails instead of creating a replacement. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/SecurityHub/AutomationRule.ts — 0 comments posted, 8 evaluated, 8 filtered
getRulemaps onlyRules?.[0]and ignores the documentedUnprocessedAutomationRulesresponse frombatchGetAutomationRulesat line 146. When an existing ARN is temporarily or selectively unprocessed, it is misclassified as absent; reconciliation then falls through to name discovery/create and can create a second active rule instead of retrying or failing. [ Exceeded comment limit ]getRulemapsInvalidAccessExceptiontoundefinedat line 150, andlistRulessimilarly maps access failure to an empty list. A role that is allowed to create rules but not to batch-get/list them will therefore see an existing rule as absent and create another rule on every deployment, accumulating duplicate active automation rules until the quota is exhausted instead of surfacing the permission error. [ Exceeded comment limit ]listRulestreatsInvalidAccessExceptionas an empty account at lines 171–174. Since AWS uses this error for lack of permission,AutomationRuleProvider.list()will tellalchemy unsafe nukethat there are no rules and skip all deletion when credentials can list neither rules nor access the Security Hub account, silently leaving active automation rules behind. [ Exceeded comment limit ]readTagsconverts every failure fromlistTagsForResourceinto an empty tag map at line 180. A transient/permission/API error therefore makes the subsequentdiffTagstreat every existing non-Alchemy tag as removed and calluntagResource, silently deleting cloud metadata rather than failing safely. [ Failed validation ]listRulesmatch byRuleNameas the resource identity at lines 205 and 230, but Security Hub permits duplicate rule names (the API's own example lists twosample ruleentries). Declaring two Alchemy rules with the same explicitruleNamecan therefore make both state rows point to one ARN, update the wrong rule, and leave the other unmanaged; the same ambiguity occurs during state recovery/adoption. [ Exceeded comment limit ]batchUpdateAutomationRulescan return HTTP success with the target inUnprocessedAutomationRules, but this response is ignored at line 261. The provider then returns as if the update succeeded (and may persist stale attributes), so a rule can remain with its old criteria/actions/status without the deployment surfacing a failure. [ Exceeded comment limit ]batchDeleteAutomationRulesalso reports per-ARN failures inUnprocessedAutomationRules, but the response is discarded and the effect succeeds at line 303. If AWS cannot delete the ARN, Alchemy removes the resource from state anyway, leaving the automation rule orphaned and still applying actions to future Security Hub findings. [ Exceeded comment limit ]InvalidAccessExceptionas successful deletion at lines 306–309. That error represents lack of permission (not proof that the rule is absent), so a role that cannot delete rules will lose the Alchemy state while the ARN remains in AWS and continues to run. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/StateStore/State.ts — 0 comments posted, 3 evaluated, 3 filtered
foo/baris stored belowfoo/, sodeleteStack({ stack: "foo" })also deletes the nested stack's objects; likewise a stage namedprod/backupis deleted by deleting stageprod. TheStateServiceaccepts these names as strings and this implementation provides no validation, so one stack/stage can silently erase another. [ Exceeded comment limit ]encodeFqn/decodeFqnpair: both an FQN such asa/band a logical ID/FQN such asa__bbecome the same object key, and decodinga__b.jsonalways yieldsa/b. Because resource IDs are accepted as arbitrary strings,list()can return a different FQN (or two resources can overwrite one another), causing state lookups and lifecycle operations to target the wrong resource. [ Exceeded comment limit ]OUTPUT_FILEis a reserved S3 key, butresourceKeydoes not reserve or escape it. A valid resource whose FQN/logical ID is__stack_output__maps to exactly__stack_output__.json;list()filters that resource out, andsetOutput/resourcesetoverwrite each other's JSON. Subsequent reads can treat stack output asPersistedStateor lose the resource/output entirely. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/VpcLattice/AccessLogSubscription.ts — 0 comments posted, 5 evaluated, 5 filtered
findByDestinationTypefilters only on the delivery service and ignores each summary'sserviceNetworkLogType, even though that value is part of the subscription and is immutable. During cold-start adoption, a same-resource subscription for the same destination service but the oppositeSERVICE/RESOURCEmode is adopted as if it matched;reconcilecannot change that mode, so the resource silently remains configured for the wrong kind of access logs. [ Exceeded comment limit ]serviceNetworkLogTypeas different from explicit"SERVICE", even though this resource documents"SERVICE"as the default. Thus changing only the spelling from omitted to explicit default (or back) plans a replacement, causing needless subscription churn and, with the create-first/recovery path above, potentially deleting the existing subscription. Compare both sides after applying the"SERVICE"default. [ Exceeded comment limit ]news.tagswithout accounting for VPC Lattice's tag limit. A user-supplied map at the API's 200-tag maximum becomes 203 tags and the create/tag operation is rejected, so this otherwise valid edge-case configuration cannot be provisioned. [ Out of scope (post-validation triage) ]serviceNetworkLogTypechanges,diffrequests a replacement, but the replacement is create-first and the resource allows only one subscription per destination type. The create therefore ends inConflictException, and this handler adopts the old subscription viafindByDestinationTypeinstead of forcing a delete-first replacement. The old subscription is then treated as the replacement generation and garbage-collected, so the deployment can delete the existing logging subscription without ever applying the requested log type. [ Exceeded comment limit ]readreturns the API'ssubscription.destinationArn, which this file documents as being normalized by AWS with a trailing:*for CloudWatch log groups, butreconcilepersists the unnormalizednews.destinationArn. With a normal log-group ARN (no suffix), every later sync comparesarn:...:log-group:name:*toarn:...:log-group:name, reports drift, and re-enters reconcile even thoughnormalizeDestinationArnsays they are equal. Persist a canonical form or normalize the read attribute. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/AWS/Website/StaticSite.ts — 0 comments posted, 1 evaluated, 1 filtered
StaticSitenow delegates builds toCommand.Build, whoseCommandExecutordefaultsshelltofalse, butStaticSiteBuildPropshas noshellfield and this call does not set it. The previousBuild.Commandimplementation always executed the command through a shell, so valid build commands such ascd app && npm run build, pipes, redirects, environment assignments, or quoted arguments now get split into literal argv tokens or fail withENOENT; any site using those commands cannot build or deploy. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Bundle/Vite.ts — 0 comments posted, 2 evaluated, 2 filtered
id.startsWith(root)rather than a path-boundary check. For a project rooted at/tmp/app, a linked workspace module at/tmp/application/...is outside the project but still passes this prefix test and is omitted frommaybeExternalWorkspaces.hashViteInputthen hashes only the root, so changes in that external workspace can be missed and a cached deployment can reuse stale worker output. [ Exceeded comment limit ]fileNameinsertspath.relative(...)directly into Cloudflare Worker module names. On Windows the path service returns backslash-separated paths (for exampledist\ssr), while the bundle contract requires POSIX module paths and the generated JavaScript imports use/. The resulting module names no longer match the import specifiers, so Vite/Cloudflare deployments built on Windows can fail to resolve chunks at worker startup. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/AI/GatewayDynamicRouting.ts — 0 comments posted, 2 evaluated, 2 filtered
findByNameonly examines the first 50 routes, even though the same provider explicitly paginateslistDynamicRoutingsinlistRoutes. With more than 50 routes and the target route on a later page, a lostrouteIdis treated as absent; reconcile then attempts a duplicate create, and itsRouteAlreadyExistsrecovery uses the same incomplete lookup and rethrows instead of recovering the existing route. [ Exceeded comment limit ]RouteAlreadyExistshandler unconditionally deletes whatever routefindByNamereturns before retrying the patch. If the requested name is already used by a legitimate, independently managed route (not a stale gateway ghost), this destroys that route and then transfers its name to the current route. The code has no ownership check that would justify deleting the holder. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Access/Application.ts — 0 comments posted, 4 evaluated, 4 filtered
readpath treats any failure fromobserveByIdasundefined(the helper swallows errors generically), andundefinedis the engine's “resource is missing” signal. A transient GET failure such as a timeout or throttling response can therefore make a still-existing application enter the create path; if the subsequent create succeeds, Cloudflare gets a duplicate application and the persisted ID/audience is replaced, potentially breaking JWT validation and orphaning the original app. [ Exceeded comment limit ]Unowned(attrs)for a domain match, butApplicationProps.adoptis never read by this provider or mapped into the engine'sResource.Adoptpolicy. Consequently an application declared withadopt: truestill fails withOwnedBySomeoneElseunless the unrelated global--adopt/adopt(true)scope is supplied, so the provider's documented per-application adoption option has no effect. [ Exceeded comment limit ]bodyEqualsObservedto decide whether to call the update API, but its policy comparison only checks policy IDs/order (and sometimes precedence). The provider accepts per-application policy overrides such asapprovalRequired,isolationRequired,purposeJustificationRequired,sessionDuration, andapprovalGroups; changing any of those while keeping the same policy ID makes this condition report “in sync”, so the new override is never sent to Cloudflare. [ Exceeded comment limit ]deletecatches every failure fromdeleteAccessApplicationForAccountand turns it into success. A permission error, rate-limit/network failure, or any other non-NotFound error therefore still lets the engine delete the resource's state, leaving the Access application orphaned in Cloudflare while future deploys no longer know to manage it. Only an explicitly handled not-found condition should be ignored. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Access/Policy.ts — 0 comments posted, 3 evaluated, 3 filtered
findPolicyByNameas success. For example, a validation, authorization, or service error duringcreateAccessPolicyis silently converted into adoption of an existing same-named policy (and the later sync may PUT into it), hiding the original create failure and potentially mutating the wrong resource. [ Exceeded comment limit ]Policy.deleteconverts every failure fromdeleteAccessPolicyinto success viaEffect.catch(() => Effect.void), rather than only ignoring a not-found response. A revoked/invalid credential, permission error, network failure, or Cloudflare outage therefore removes the resource from Alchemy state even though the Access policy remains deployed and may continue affecting applications. [ Exceeded comment limit ]Unownedbranding or another ownership check. The planner therefore silently adopts that foreign policy when adoption is disabled, andreconcilecan then PUTnewsinto it. A stack with a collidingnamecan consequently overwrite another policy's Access decision/rules instead of failing or requiringadopt. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Access/ServiceToken.ts — 0 comments posted, 7 evaluated, 7 filtered
previousClientSecretExpiresAtis documented as independently mutable, butdiffonly checks the account and otherwise returnsundefined. Changing just this property therefore never schedulesreconcile, so users cannot extend or immediately expire the previous secret unless they also change an unrelated field or bumpclientSecretVersion. [ Failed validation ]output,readreturns the token withclientSecret: undefined, andreconcilelikewise reuses a name match without rotating it; a fresh deploy against a pre-existing token (or lost state) therefore succeeds but exposes an unusable credential to downstream bindings. The version fallback can also suppress rotation when it comes fromolds. [ Exceeded comment limit ]readfallback also treats a same-name token as the cached token after the persisted ID disappears. Because names are not unique, a replacement/unrelated token can be selected and returned withoutput?.clientSecretfrom the deleted token, producing a persistedclientId/secret mismatch while the plan may otherwise be a no-op. Recovery by name must not be used as proof of identity for this write-only secret. [ Exceeded comment limit ]serviceTokenIdis gone, reconcile falls back to an exact-name lookup even though the implementation explicitly allows duplicate names. If token A is deleted out of band and token B with the same name exists, the provider adopts B but keepsoutput.clientSecretfrom A (unless a version bump happens); state then pairs B'sclientIdwith A's secret and may also rename/rotate the unrelated token. This corrupts credentials and can mutate the wrong service token. [ Exceeded comment limit ]findTokenByNameis wrapped in a catch-all that converts any list failure intoundefined. Inreconcile, a transient API/rate-limit/permission failure at line 242 is consequently interpreted as absence and followed bycreateAccessServiceTokenForAccount, which can create a duplicate same-named token (the API does not enforce name uniqueness), leaving the original token orphaned and changing which credential the stack uses. [ Exceeded comment limit ]Effect.catcharoundcreateAccessServiceTokenForAccounttreats every failure as a same-name create race. A transient/network, permission, or validation failure therefore falls through tofindTokenByName; if any unrelated token has that name, the provider adopts it and returns success with no newly captured secret (output?.clientSecretis usually undefined). This silently binds the stack to the wrong credential instead of surfacing the create failure. [ Exceeded comment limit ]previousClientSecretExpiresAtand invokesreconcile, the sync condition only checksnameandduration. With those unchanged and no version bump, no PUT/rotate request is made, so the previous secret's expiration is never extended or revoked for a change to this property alone. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Addressing/AddressMap.ts — 0 comments posted, 2 evaluated, 2 filtered
AddressMapProvidermarksaccountIdstable but never supplies adiffthat compares it with the activeCloudflareEnvironmentaccount. If the same stack/state is deployed after switching profiles, the plan sees unchanged props and treats the resource as a no-op, leaving the persisted map bound to the old account; later reads/deletes use that old account id and fail (and no map is created in the new account). Account-scoped providers need to returnreplacewhenoutput.accountId !== accountId. [ Exceeded comment limit ]list()returns immutable Cloudflare-managed maps (canDelete: false), but this provider is not markednuke: { skip: true };delete()then immediately succeeds at line 334 without deleting them. Consequentlyalchemy unsafe nukeincludes these maps in its targets and reports them as deleted even though they remain in the account, so repeated/account-wide teardown cannot accurately clean up or report the result. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Containers/LocalContainerProvider.ts — 0 comments posted, 4 evaluated, 4 filtered
dockerfileis documented as relative tocontext, but this branch resolves it directly withfs.realPath(news.dockerfile), which resolves relative to the process working directory. For the documented{ context: "./app", dockerfile: "Dockerfile" }form, it looks for./Dockerfileinstead of./app/Dockerfileand either fails the local deployment or produces an out-of-context path. Resolve the Dockerfile against the resolvedcontext(while preserving absolute paths). [ Exceeded comment limit ]Artifacts.cachedcan permanently hang later lifecycle calls after a failed first build.Artifacts.cachedstoresDeferred.await(deferred)before running the effect and only completes it on success; any bundling, filesystem, or Dockerfile error leaves the deferred unresolved. Because the local RPC provider process persists across retries, the nextprepareImagecall reads that cached effect and waits forever, making subsequentalchemy devattempts hang until the provider is restarted. [ Exceeded comment limit ]prepareImageis memoized only bycontainer-image:${id}at line 139, but local providers run in the long-lived RPC server whoseArtifactStorepersists across dev deploys. After the first build, editing themainbundle or any file in an external Docker context causesdiffto receive the old hash fromArtifacts.cached, so it returns no update and the running container continues executing stale code until the RPC server is restarted. The cache key must include current source content or be scoped to a single deploy rather than the persistent provider process. [ Exceeded comment limit ]precreatereceives raw props containing unresolvedOutputs, butmakeAttributespasses those props tomakeContainerEnvbefore the inputs are evaluated.makeContainerEnvskips unresolved values underprops.envbut does not skipenvironmentVariables[].value, so a valid value such as{ name: "BUCKET", value: bucket.bucketName }is copied as anOutputintodev.env. The worker consumes the precreatedevobject to start the local container before the final reconcile, causing an unresolved object to be handed to the Docker/runtime environment instead of the resolved string. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/CustomCertificate/CustomCertificate.ts — 0 comments posted, 5 evaluated, 5 filtered
certificateIdanduploadedOnare declared stable, but Cloudflare's Custom Certificates API explicitly returns a new certificate ID and deletes the old ID when asni_customcertificate is PATCHed. During a rotation, downstream resources therefore receive the old deleted ID as their stable reference and can keep configuring or observing a certificate that no longer exists. These fields must not be treated as stable for that update path (or the provider must model the replacement). [ Exceeded comment limit ]sni_customID and returns a new one from a successful PATCH. If persistence fails after that PATCH, the next run still has the old ID;getCertificatereturnsundefined, and this branch uploads another certificate instead of recovering the already-created replacement. The patched certificate is then orphaned and future runs can accumulate duplicates. [ Exceeded comment limit ]output.certificateId; when thecreating/createdstate commit fails aftercreateCustomCertificatesucceeds, a retry has no output ID and falls through to another upload. Custom certificates have no uniqueness constraint, so this creates an orphaned duplicate certificate (and the original is not recoverable through state). The provider must recover by a deterministic lookup or otherwise make the greenfield retry idempotent. [ Failed validation ]deployis accepted as a mutable PATCH field, but it is neither included indesiredHashnor compared in the dirty checks. If a deployed certificate changes fromstagingtoproduction(or vice versa) while the PEM/key are unchanged,certDirtyand all other dirty flags remain false, so no PATCH is sent and the certificate stays in the old deployment environment. [ Exceeded comment limit ]bundleMethodis documented with a default of"ubiquitous", but this dirty check only runs whennews.bundleMethodis explicitly defined. After a certificate was deployed with"force"or"optimal", removing the property leaves the non-default bundle in Cloudflare forever because the provider neither detects the change nor patches it back to"ubiquitous". [ Failed validation ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/CustomHostname/CustomHostname.ts — 0 comments posted, 5 evaluated, 5 filtered
listreturns every custom hostname in every zone as ordinaryAttributes, with no ownership filtering orUnownedmarker. Providerlist()is the input toalchemy unsafe nuke, which passes each returned attribute directly todelete; therefore running nuke with this provider enabled deletes unrelated customers' custom hostnames, not just resources created by the current stack. [ Exceeded comment limit ]Effect.catch, which catches every typed create failure rather than only an already-exists/conflict error. If creation fails for a validation, quota, permission, or transient server error andfindByHostnamehappens to see a matching existing hostname, the provider treats that failed create as success, may PATCH that existing resource, and returns successful attributes instead of surfacing the original failure. [ Exceeded comment limit ]readsees no hostname, another actor creates that hostname beforecreateCustomHostname, and the create failure is followed by a successfulfindByHostname, this code adopts the found object asobservedand proceeds to PATCH it without anUnowned/adoptcheck. Because custom hostnames intentionally have no ownership marker, an out-of-band customer hostname can be modified merely by racing deployment. [ Exceeded comment limit ]justCreatedis set totrueafter the entire create-and-recovery expression, including the branch wherecreateCustomHostnamefailed and the handler returned an already-existing hostname fromfindByHostname. In that race/recovery case the resource was not created withdesiredSsl, but!justCreatedsuppresses the SSL PATCH, leaving the recovered hostname permanently unconverged until a later run. [ Exceeded comment limit ]news.ssl, but the comparison atsslEqualsObservedignorescloudflareBranding,customCertificate,customKey, andcustomCsrId(andnarrowHostnamenever retains those observed fields). After any of these supportedSslinputs changes on an existing hostname, this condition remains equal and no PATCH is sent, so the Cloudflare certificate configuration silently stays stale. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Devices/CustomProfile.ts — 0 comments posted, 3 evaluated, 3 filtered
nameand does not excludematch.default === true. If the requested name equals the account's default profile name,readreturns that singleton as anUnownedcustom profile; after adoption,reconcileuses itspolicyIdand PATCHes/deletes it through the custom-profile provider, allowing a custom resource to mutate the account-wide default WARP policy. [ Failed validation ]listenumerates every item fromlistDevicePolicyCustomsbut never filters out the account's default profile (p.default). Cloudflare's collection includes that singleton, solist()exposes it as aDeviceCustomProfile; account-wide teardown/import can then treat the non-deletable default policy as a custom resource and invoke this provider's delete path against it, risking a failed or destructive default-profile operation. [ Exceeded comment limit ]list, a profile is only converted toundefinedwhen the mainobserveProfileGET seesDevicePolicyNotFound. If the profile is deleted after that GET but beforebuildAttrsfetches its three list endpoints,observeListspropagatesDevicePolicyNotFoundand aborts the entire account enumeration instead of dropping that vanished item as the comment promises. [ Out of scope (post-validation triage) ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Dns/Record.ts — 0 comments posted, 3 evaluated, 3 filtered
news.namedirectly tofindByNameType, whose exact client-side comparison does not normalize names. Cloudflare returns DNS names in Punycode even when created with Unicode input; therefore a valid Unicode record is not recognized on a cold read or after state loss, and reconcile can create a second record (or miss the adoption safety check). Normalize both requested and observed names before matching. [ Exceeded comment limit ]DnsRecordAlreadyExistsrecovery treats any record found after the initial scan as safe to adopt and then updates it. If another stack or an operator creates the(name, type)record betweenread/findByNameTypeandcreateRecord, this branch runs even whenadoptis false and overwrites the existing record's content and mutable fields. The race recovery must preserve the ownership gate instead of mutating a record merely because the API reported a conflict. [ Exceeded comment limit ]deleteconverts every failure fromdns.deleteRecordinto success viaEffect.catch(() => Effect.void), not just an already-missing record. A permission error, outage, or other failed deletion therefore causes the engine to remove the resource from state while the DNS record remains live, leaving teardown falsely successful and the record unmanaged. Catch only the documented not-found cases and propagate other failures. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Gateway/Rule.ts — 0 comments posted, 4 evaluated, 4 filtered
RuleProvider.diffonly checksactionand never compares the persistedoutput.accountIdwith the currentCloudflareEnvironmentaccount. Because provider stables do not themselves trigger a diff, redeploying the same stack/state against a different Cloudflare account can be planned as a noop, leaving the old account's rule attributes and ID in state instead of replacing or reconciling the rule in the new account. [ Exceeded comment limit ]ruleIdexists,readreturnsundefined, butreconcilethen unconditionally callsfindRuleByNameand treats any match as its own rule. Thus a first deploy with a colliding existing Gateway rule bypasses the planner's adoption/ownership check and updates that foreign rule even when adoption is disabled, potentially changing enforcement behavior. [ Exceeded comment limit ]createRulefallback catches all errors and treats a same-named list result as the successfully created rule. A non-conflict failure such as invalid request data, authorization failure, or a transient server error can therefore be hidden and converted into an update of an existing rule, rather than re-failing the original error or only recovering a duplicate-create race. [ Exceeded comment limit ]Rule.deleteswallows everydeleteGatewayRulefailure withEffect.catch(() => Effect.void). If deletion fails for authorization, transport, or service-availability reasons, the engine can mark the destroy successful and discard state while the Gateway rule remains active, leaving enforcement in Cloudflare and an orphaned resource. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Intel/IndicatorFeed.ts — 0 comments posted, 2 evaluated, 2 filtered
createIndicatorFeedreturns success without anid(the Cloudflare API schema makes bothresultandresult.idoptional), the fallback usesfindByName(accountId, name), which intentionally selects the oldest matching feed. Because indicator-feed names are not unique, if a prior feed already has that name this selects the wrong feed rather than the one just created; subsequent updates/snapshot upload mutate the pre-existing feed and the newly created undeletable feed is orphaned. A missing-id create response should be treated as unrecoverable or correlated more safely. [ Exceeded comment limit ]list, but itsdeleteimplementation is only a warning/no-op because feeds have no delete API. Without declaringnuke: { skip: true },alchemy unsafe nukeincludes every indicator feed, invokes this no-op, and reports it as deleted even though it remains on the account (and will be discovered again on the next nuke). This makes account-wide cleanup silently incomplete and misleading. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/KV/ReadNamespace.ts — 0 comments posted, 2 evaluated, 2 filtered
ReadNamespaceClientmakesrawan unconditional public operation, but the HTTP and local implementations construct this client withraw: Effect.die(...)because they have no nativeKVNamespace. Any caller using the advertisedclient.rawwithReadNamespaceHttporReadNamespaceLocalgets a runtime defect instead of a typed failure; make this capability optional or expose it only on the native-binding client type. [ Exceeded comment limit ]getWithMetadataoverloads advertiseMap<string, KVNamespaceGetWithMetadataResult<...>>, but the non-native implementations fulfill array requests by calling the value-only bulk endpoint and wrapping every entry withmetadata: null. ThusReadNamespaceHttp/ReadNamespaceLocalsilently discard stored metadata forgetWithMetadata([...]), unlike the declared client contract and native KV behavior. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/MagicCloudNetworking/OnRamp.ts — 0 comments posted, 3 evaluated, 3 filtered
outputpresent butoldsundefined), the replacement checks forregion,cloudAsn,adoptedHubId, andhubProviderIdnever run because they are insideif (old !== undefined). Those properties are immutable according to this provider, yetreconcilethen observes the adopted resource and only patches mutable fields, silently retaining an existing on-ramp with a different region/ASN/hub identity while reporting success. [ Exceeded comment limit ]OnRampAttributespersistsaccountId, and all API endpoints are account-scoped, butdiffhas no check thatoutput.accountIdmatches the currentCloudflareEnvironmentaccount. If a stack is redeployed under another account,reconcilefirst reads the old on-ramp usingoutput.accountId, then patches it with the newaccountId(or returns it as if it belonged to the new account on a no-op), rather than replacing it; this can fail reconciliation or associate the old-account resource with the new stack. [ Exceeded comment limit ]description,vpc, attachment arrays, and management flags are only compared when their correspondingnewsfield is defined. After a prior deployment sets (for example)descriptionorattachedVpcs, removing that property from the desiredOnRampleaves the old cloud value in place and returns it as converged because no patch is issued. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/MagicNetworkMonitoring/Rule.ts — 0 comments posted, 3 evaluated, 3 filtered
news.accountIdis a string. If a rule was deployed with an explicit account ID and the prop is later removed while the ambient profile now points at another account, the effective account changes but this diff returns no replacement.reconcilethen looks in the new account and may create/adopt a second rule while leaving the original rule orphaned in the old account. [ Exceeded comment limit ]outputexists. If the managed rule is deleted out of band and another actor creates a new rule with the same unique name,readreturns that replacement as plain attributes, bypassingUnowned; the next reconcile can patch it (or destroy it) without the adopt policy. The name-only path cannot prove ownership, just as the cold-read comment acknowledges. [ Exceeded comment limit ]dirtyonly compares optional rule fields when the new value is defined. If a deployed threshold rule hasduration: "5m"(or a threshold/zscore setting) and the property is later removed from the desiredRule, every guarded comparison is false, so reconciliation returns the observed rule without callingpatchRule; the old Cloudflare setting remains indefinitely. This also violates the documenteddurationdefault of"1m"when it is removed. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/MagicTransit/SiteLan.ts — 0 comments posted, 7 evaluated, 7 filtered
diffexits wheneveroldsisundefined, but the provider contract explicitly uses that state for an adopted resource that already hasoutput. As a result, an adopted LAN whoseoutput.siteIddiffers fromnews.siteId, or whoseoutput.haLinkdiffers from the desiredhaLink, is treated as an in-place update.siteIdcannot be changed in place andhaLinkis create-only, so reconciliation either targets/creates under the wrong site or leaves the adopted LAN's HA setting unchanged while recording it as converged. [ Failed validation ]haLinkcomparison usesolds.haLinkrather than the observedoutput.haLink. If the LAN is changed out of band fromfalsetotrue, a desiredhaLink: falsestill compares as equal, andupdateSiteLanhas nohaLinkfield to repair it. The provider then persists the observedtruevalue as if reconciliation succeeded and continues to miss the drift on later plans. [ Exceeded comment limit ]outputfrom account A and the environment now targeting account B,reconcilelooks upoutput.lanIdonly in B, fails to find it, then creates or adopts a same-named LAN in B while the LAN in A remains unmanaged. SinceaccountIdis included in stable attributes and the endpoint is keyed by account, this should be a replacement rather than a silent new resource/orphan. [ Exceeded comment limit ]createSiteLan, the API returns the entire site's LAN array and LAN names are explicitly not unique, but this code selects the first LAN matchingname(or even the first LAN overall). If two creates for the same name race, both responses can contain both LANs and this selects the lexicographically/response-first pre-existing LAN rather than the LAN just created. The resource then persists the wronglanId, so later updates/deletes can mutate or destroy another LAN and leave the newly-created one orphaned. [ Exceeded comment limit ]vlanTagfrom30to omitted (documented default0), removingroutedSubnets, or omittingstaticAddressingto return to DHCP leaves the existing LAN configuration untouched because every such comparison is guarded bynews.<field> !== undefined; the provider then reports the stale LAN as reconciled. [ Exceeded comment limit ]dirtycalculation does not compare the full mutable nested configuration. It only checksnat.staticPrefix, routed-subnetprefix/nextHop(viasameRoutedSubnets), andstaticAddressing.address; changes to routed-subnet NAT,secondaryAddress,virtualAddress, DHCP relay/server fields, or reservations therefore skipupdateSiteLanand are reported as converged even though the desired LAN configuration changed. [ Exceeded comment limit ]list()only catchesMagicWanUnauthorizedfor both the account site enumeration and each site's LAN enumeration. The same Magic WAN calls can return the typedForbiddenerror for a token with partial/insufficient scope (the adjacentSiteWanprovider handles both tags, and the LAN tests document this case). In that situation provider listing fails instead of returning the intended empty/partial result, breaking operations such as account-wide enumeration on otherwise valid credentials. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/R2/BucketEventNotification.ts — 0 comments posted, 3 evaluated, 3 filtered
diffnever compares the persistedoutput.accountIdwith the currentCloudflareEnvironmentaccount. After deploying this account-scoped configuration and switching the profile to another account, the provider reports no replacement;reconcilecontinues usingoutput.accountId, so the new account is not managed and the old account's notification remains attached. [ Exceeded comment limit ]outputdoes not prove that the configuration currently returned bygetConfigurationis the one previously managed. If the old config is deleted out of band and another actor recreates the same(bucketName, queueId, jurisdiction)pair, this branch returns it as owned (output ? attrs : Unowned(attrs)), allowing the next reconcile to delete and replace that actor's rules without adoption approval. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/ResourceSharing/Share.ts — 0 comments posted, 4 evaluated, 4 filtered
reconcilemay rename it, remove its recipients/resources, ordeleteit during destroy. This lookup must not silently claim ownership without a positive identity check (or must be gated as unowned). [ Exceeded comment limit ]outputis absent,reconcilealways callscreateResourceSharingwithout first looking up the deterministic name. If the create succeeds but the subsequent state commit fails or the process is interrupted, the retry enters this branch again and creates another share. The code explicitly notes that names are not unique, so Cloudflare cannot deduplicate this retry; repeated deploys can leave orphaned duplicate shares. [ Exceeded comment limit ]desired.organizationIdis compared tor.accountId, but Cloudflare's recipient list/get response exposesaccount_idand does not exposeorganization_id. For a share declared with{ organizationId: ... }, the live association therefore does not match, so each update tries to create it again; the removal loop also treats the existing association as unwanted and deletes it. This causes repeated recipient churn (or duplicate/create failures) for organization-targeted shares. [ Failed validation ]resourceTypeandresourceIdand, when a match is found, updates onlymeta. It never compares the desiredresourceAccountIdwith the live entry. Becausediffdoes not replace onresourceschanges, changingresourceAccountId(including from the default current account to an explicit owner) runs this path but leaves the old owner in Cloudflare, so the share silently does not converge to the declared resource. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Ruleset/CustomRuleset.ts — 0 comments posted, 1 evaluated, 1 filtered
output === undefinedbranch unconditionally callscreateRulesetForAccountand does not re-find an existing ruleset by the deterministic(name, phase, kind)identity. A successful Cloudflare create followed by a failed/interrupted state commit is retried with no output, creating another ruleset instead of converging. This violates the provider's required idempotent reconcile behavior and can orphan duplicate account rulesets. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Rum/Rule.ts — 0 comments posted, 2 evaluated, 2 filtered
listRulesfailures taggedForbiddenare converted to an empty result for that ruleset, with no retry or indication that enumeration was incomplete. Sinceprovider.list()feeds account-widealchemy unsafe nuke, a transient or permanent 403 causes every rule in that ruleset to be omitted and therefore never passed todelete; the nuke run can report completion while leaving those rules behind. Retry transient 403s and surface an unresolved listing (or otherwise make the omission explicit) instead of treating it as[]. [ Exceeded comment limit ]outputpoints at a ruleset that was deleted out of band (for example, its RUMSitewas deleted),listRulesreturnsundefinedand this branch treats that as a missing rule. It then callscreateRulewith the deletedrulesetId, but the Cloudflare create endpoint requires an existing ruleset, so every update/recovery deploy fails instead of recreating or otherwise recovering the parent/ruleset. A missing ruleset must not be conflated with an absent rule. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/SecretsStore/Secret.ts — 0 comments posted, 1 evaluated, 1 filtered
SecretNameAlreadyExistsrecovery blindly adopts and patches the name-matching secret after the initialreadmissed it. A concurrent stack or out-of-band creator can win betweenreadandcreateStoreSecret; with adoption disabled, this path still sendsnews.valueto that foreign secret, irreversibly overwriting its write-only value. Re-check ownership (or fail unless adoption is explicitly enabled) before patching the raced secret. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Tags/ZoneResourceTags.ts — 0 comments posted, 1 evaluated, 1 filtered
tags: {},desiredis empty andrecordsEqualreturns true whengetZoneTagreports the normal empty set, so reconcile returns a successfulZoneResourceTagsAttributeswithout issuing a PUT. Howeverreadtreats that same empty set asundefinedat line 260. Thus an empty-tag configuration produces state for a resource that does not exist and is lost on the next refresh/import; either reject empty tags or consistently model the empty set as present. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Tunnel/Route.ts — 0 comments posted, 5 evaluated, 5 filtered
reconcilecallsobserveby account/network before checkingnews.adopt, andobservedeliberately scans routes attached to any tunnel. Thus, when a pre-existing route owns the requested CIDR, a resource withadopt: falsestill treats that foreign route as its own and the later sync PATCH can move it tonews.tunnelIdand overwrite its comment. Theadoptflag is therefore bypassed for the normal pre-existing-route path, allowing one stack to take over another stack's route. [ Exceeded comment limit ]reconcileobservation path looks up only CIDR and optional VNET, not the route type. Cloudflare's network-route collection contains non-cfd_tunnelroutes (for example WARP connector and Magic routes), so a same-CIDR route of another type can be returned asobserved; the subsequent PATCH sends the requestedtunnelIdand the provider persists it as a Tunnel Route. This can hijack or corrupt unrelated connectivity during an ordinary reconcile, independently of theadoptflag. [ Exceeded comment limit ]news.virtualNetworkIdis omitted,reconcilepassesundefinedto the account-wide lookup. The lookup then accepts a matching CIDR from any virtual network, even though Cloudflare uses the account's default virtual network when the create request omitsvirtual_network_idand permits the same CIDR in separate virtual networks. If that CIDR exists only in a non-default VNET, the provider adopts/patches that route instead of creating the desired default-VNET route, moving traffic and recording the wrongvirtualNetworkIdin state. [ Exceeded comment limit ]deletehandler catches every failure fromdeleteNetworkRoute, not just an already-missing route. Permission errors, rate limits, and transient Cloudflare/5xx failures are therefore reported as successful destroys while the route remains deployed, leaving state and cloud connectivity inconsistent with no retry or operator-visible failure. [ Exceeded comment limit ]listenumerates every account-wide network route but does not filtertunTypestocfd_tunnel. Cloudflare's route collection also returnswarp_connector,warp,magic,ip_sec,gre, andcniroutes; the provider maps all of them into this resource's attributes. Since provider listings feed account-wide teardown and each mapped item is delete-ready,alchemy unsafe nukecan pass non-Cloudflare-Tunnel routes todeleteNetworkRouteand remove unrelated connectivity. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Cloudflare/Tunnel/Tunnel.ts — 0 comments posted, 2 evaluated, 2 filtered
listmakesTunnelProviderresources eligible foralchemy unsafe nuke, but the provider'sdeletestill wraps the API call inEffect.catch(() => Effect.void). Any real deletion failure (permission, rate limit, dependency conflict, or transient outage) is therefore reported to nuke as success, so it emitsdeletedwhile the tunnel remains in Cloudflare and is not retried or surfaced to the operator. Only an idempotent not-found result should be swallowed. [ Exceeded comment limit ]createTunnelCloudflaredsucceeds, the result is assigned toobservedwithout checking that it has anid. The Cloudflare response schema allowsresult.idto be absent, and the subsequentobserved.id!calls passundefinedtogetTunnelCloudflaredToken(and possiblywriteConfiguration) while returning an undefinedtunnelId; the deploy then fails or persists unusable state, leaving the newly created tunnel orphaned. Re-read by name or fail explicitly when creation returns no id. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/GitHub/Environment.ts — 0 comments posted, 2 evaluated, 2 filtered
news.deploymentBranchPolicy.customBranchPolicies. If a config contains the same pattern twice,observedNamesis never updated after the first create, so the loop POSTs the duplicate and GitHub rejects it (the API documents a 303 for an existing pattern), causing the whole reconciliation to fail instead of converging. [ Out of scope (post-validation triage) ]listemits only environment attributes (environmentId,nodeId,name, etc.) and omits the requiredownerandrepositorylocation, but account-wide nuke passes each listed item asoldstodelete. ConsequentlydeleteAnEnvironmentreceivesowner: undefinedandrepo: undefinedfor enumerated environments, so nuke cannot delete GitHub environments discovered by this provider. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Planetscale/MySQL/MySQLPassword.ts — 0 comments posted, 2 evaluated, 2 filtered
listBranches.pages(...)does not handle PlanetScale's documented 403 Forbidden response. Because it is inside the outerEffect.forEach, one database that the token cannot read causes the whole providerlist()to fail; the nuke scanner then converts that provider failure to an empty inventory, so passwords in all other accessible databases are skipped as well. Catch the per-database permission error (or otherwise retain partial results) rather than failing the full scan. [ Exceeded comment limit ]listPasswordsis only wrapped withEffect.catchTag("NotFound", ...), but PlanetScale documents a 403 Forbidden response when the credential lacks password-management/connect access. Duringlist()(used by account-wide enumeration/nuke), one inaccessible branch therefore fails the entire provider instead of being skipped, so nuke/list cannot operate in organizations where the token can enumerate databases/branches but not every branch's passwords. HandleForbidden(as the Postgres role provider does) or otherwise preserve partial results. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Planetscale/Postgres/PostgresDefaultRole.ts — 0 comments posted, 1 evaluated, 1 filtered
listBranches.pages(...)does not handle PlanetScale's documented 403 Forbidden response. Since it runs inside the outerStream.flatMap, a database inaccessible to the token terminates the entire default-role inventory stream; nuke then sees no default roles at all for this provider and leaves them undeleted. Handle a forbidden branch/database as an empty branch set (while preserving other databases) instead of failing the full scan. [ Exceeded comment limit ].repos/alchemy-effect/packages/alchemy/src/Sync.ts — 0 comments posted, 1 evaluated, 1 filtered
provider.readreturns anUnownedvalue, this code removes the ownership marker and treats the result as ordinary live state. It then callsprovider.reconcileon drift without consultingAdoptPolicy. Providers useUnownedspecifically when they found an existing cloud object but cannot prove this stack owns it, so a sync of a previously persisted resource can overwrite a foreign object's configuration (for example after a resource is deleted/recreated or its ownership tags change), bypassing the adoption safety gate. [ Exceeded comment limit ].repos/alchemy-effect/scripts/aws-leak-sweep.ts — 0 comments posted, 2 evaluated, 2 filtered
/fooandfooas distinct parameter names, but ARN parsing can yield the sameparameter/fooresource suffix; whenrawisfoo, this code storesfooanddestroyLeaklater callsDeleteParameterwithName: "foo", not/foo. A tagged/fooleak is therefore left behind, and if both names are present the cleanup can target the wrong parameter. Keep the original parameter name from the resource metadata or handle the leading-slash ARN representation explicitly. [ Exceeded comment limit ]discoverSchedulesto the deletion set. That discovery only checks whether the name contains-${stage}-; it does not verify any Alchemy tag or stack ownership. Running this tool with--deletecan therefore delete unrelated user-created EventBridge schedules whose names happen to contain-test-(or the selected stage), causing service outages and permanent schedule loss. [ Exceeded comment limit ]apps/desktop/scripts/probe-turnstile.cjs — 0 comments posted, 3 evaluated, 3 filtered
BrowserWindowwithshow: falseand never focuses it, butclickPointrelies onwebContents.sendInputEvent. Electron documents thatsendInputEvent()only works when the containingBrowserWindowis focused, so both the email-field click and submit click are discarded in this probe. The later JS fallbacks can still submit the form, but no trusted mouse interaction is exercised, making the Turnstile/input-behavior result misleading. [ Out of scope (post-validation triage) ]chromeandchrome-runtimemodes attach the DevTools debugger twice: the generic branch at lines 75–78 already attaches for both modes, then the Chrome-specific branch callsdebugger.attach("1.3")again at line 92. Electron rejects a second attach, so either mode exits through the catch beforeNetwork.setUserAgentOverride(and before its intended probe) can run. [ Out of scope (post-validation triage) ]runModecatches every navigation, DOM, debugger, and preload failure, writes it to the JSONL file, and then returns normally. The caller subsequently executesapp.exit(0)regardless of those errors, so a failed probe (for example a missing preload or timed-out Auth0 navigation) still has a successful process exit status and cannot fail CI or a shell-based compatibility check. [ Out of scope (post-validation triage) ]apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt — 0 comments posted, 1 evaluated, 1 filtered
onComposerSubmitevent. The sharedComposerEditorAPI passesonSubmitfor hardware Command-Return, but thisEventslist omits it andSelectionAwareEditTexthas no key/editor-action handler, so Command-Return on Android is consumed as ordinary multiline input (or ignored) and cannot trigger send. [ Cross-file consolidated ]apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt — 0 comments posted, 3 evaluated, 3 filtered
setScrollEnabled(false)only togglesisVerticalScrollBarEnabled, which controls whether the scrollbar is drawn, not whether theEditTextcan scroll. The publicscrollEnabledprop therefore has no effect on actual scrolling; a multiline editor with scrolling disabled can still scroll its content (and the collapsed composer can move its text out of view). [ Exceeded comment limit ]updateInputFlags()conflates the independentautoCorrectandspellCheckprops: unless both are true it addsTYPE_TEXT_FLAG_NO_SUGGESTIONS. Android documents that this flag suppresses dictionary candidates and overridesTYPE_TEXT_FLAG_AUTO_CORRECT, soautoCorrect=false, spellCheck=truedisables spell checking, whileautoCorrect=true, spellCheck=falsealso disables autocorrection instead of preserving it. [ Exceeded comment limit ]ComposerChipSpanis only aReplacementSpan, so it changes measurement/drawing but does not make the underlying token range atomic. Because theEditTextremains freely editable and no selection/deletion override handles this span, placing the caret in a chip and typing or pressing backspace edits one character of the serialized@.../$...source; the token then becomes partially corrupted and disappears on the next token reconciliation, unlike the atomic attachment behavior expected by the composer. [ Exceeded comment limit ]apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift — 0 comments posted, 1 evaluated, 1 filtered
droppedImageProvidersonly claims a drop when every provider is an image. A normal mixed drop (for example, an image dragged alongside text or another file) therefore returnsdrop.suggestedProposal, allowingUITextView's built-in image handling to insert anNSTextAttachment.serializedTextonly understandsComposerTextAttachment; unknown attachments are serialized as the attachment replacement character, so the image is not sent throughonComposerPasteImagesand the controlled draft receives a spurious\u{FFFC}instead. [ Exceeded comment limit ]apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift — 0 comments posted, 1 evaluated, 1 filtered
prepareShowcaseCapturedeletes every matching generic-password and internet-password item because theSecItemDeletequeries contain onlykSecClassand no app-specific service, account, or other qualifier. When a showcase build runs this at app startup, it removes unrelated credentials/tokens stored by the app, not just showcase state, with no recovery path. [ Exceeded comment limit ]apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffView.kt — 0 comments posted, 14 evaluated, 10 filtered
emitVisibleFile(first), and the Android implementation resolves index0to the first file even when the scroll offset is exactly zero.ReviewSheetusesscrollToTop()for the “All files” selection, so tapping that destination either leaves the previous file selected (if already at offset 0) or immediately selects the first file after scrolling to the top; the user cannot select the all-files state on Android. Handle the zero-offset case as a null file selection before resolving a file header. [ Exceeded comment limit ]setTokensResetKeyclearscanvasView.tokensByRowIdbut does not invalidate an in-flightsetTokensJsondecode. If a largesetTokensJsonrequest is decoding when the reset key changes, its completion still has the currenttokensDecodeGenerationand posts the old token map after the clear, so the new content can be rendered with stale syntax highlighting until another token update arrives. [ Exceeded comment limit ]contentResetKeyclears tokens and offsets but leavesrowsandvisibleRowspopulated with the previous file/section. Until the asynchronoussetRowsJsondecode finishes, the canvas continues drawing and hit-testing the old diff; a tap in that window can emit actions for the prior content, and the visible-file callback can also report an old file after the reset. Reset the displayed rows (and invalidate the old row decode) when changing content so old data cannot remain interactive. [ Exceeded comment limit ]setContentResetKeycallsresetHorizontalOffsets()without stoppinghorizontalScroller. A horizontal code fling that is in progress when the diff section changes will continue incomputeScroll()and reapply the old section's X position to the new rows, so the new section can immediately jump away from the requested horizontal reset. [ Exceeded comment limit ]setContentResetKeymarks the initial scroll pending, but immediately callsapplyPendingInitialScroll()while the previous content is still invisibleRows. That consumes the pending flag and posts an offset based on the old rows; when the newsetRowsJsonresult later replaces those rows,rebuildVisibleRows()has no pending scroll left to apply. Switching an already-populated source file (or another content set) therefore leaves the view at the old row position instead of honoring the newinitialRowIndex. Clear the old rows or preserve the pending flag until the new rows are installed. [ Exceeded comment limit ]setRowsJsonresetslastVisibleFileId, butDiffCanvasView.lastVisibleRangeis not reset andrebuildVisibleRows()does not force a visible-range notification. If a refresh replaces the diff with a different file at the same visible row indices/count,emitVisibleRange()sees the unchanged(first,last)pair and never invokesemitVisibleFile; the inspector therefore keeps the old file selection until the user scrolls. Reset the canvas visible-range cache or explicitly emit the current visible file after installing new rows. [ Exceeded comment limit ]rowsrebuilds offsets but never invalidateslastVisibleRange. If a content/section change produces the same visible index range as the previous data (especially when already scrolled to the top),onDrawsuppresses the nextonVisibleRowsChangedcallback as a duplicate. Since the wrapper resetslastVisibleFileIdon every rows update, the new section's visible file is then never sent toonVisibleFileChangeuntil the user scrolls far enough to change the range, leaving the file navigator stale. [ Out of scope (post-validation triage) ]emitVisibleRangereports indices fromrows, butrowsis the collapsedvisibleRowslist supplied byrebuildVisibleRows, while the JS highlighter consumes those indices against the original uncollapseddata.rows. After collapsing a file, every later row index is shifted; for a collapsed file larger than the highlighter overscan, scrolling into a later file causes highlighting requests to target earlier rows, leaving the actually visible code unhighlighted (and potentially highlighting the wrong rows). [ Exceeded comment limit ]floatDptreats a present but nonnumeric/null JSON value as a valid metric: AndroidJSONObject.optDoublereturnsNaNon coercion failure, and this code stores that asNaNinstead of usingfallbackPx. For example,{"rowHeight":null}makesrowHeightPxbecomeNaN; the row-height conversion then collapses rows to the minimum 1 pixel (and invalid widths/paddings can similarly corrupt drawing) rather than safely retaining the fallback style. [ Out of scope (post-validation triage) ]floatSpconverts every JSON font size withdensity, which ignores Android's user font-scale factor (scaledDensity/SP conversion). On a device with a non-default system font size, the canvas text remains at the default physical size instead of scaling with the rest of the app, so the new style path breaks the platform's text-size accessibility behavior. [ Exceeded comment limit ]apps/mobile/modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/T3TerminalView.kt — 0 comments posted, 7 evaluated, 5 filtered
themeConfigsetter reparses each new config, but the parser only replacespaletteColorswhen the new config contains at least one palette entry and never resetscursorColorValuewhencursor-coloris removed. Changing from a themed config to an empty/smaller config therefore sends stale palette/cursor values throughapplyTheme(), so the terminal keeps colors from the previous theme instead of reflecting the current props. [ Exceeded comment limit ]autoFocussetter has nooldValue/field == valueguard, so every native prop update withautoFocus = truecallsrequestKeyboardFocus(). This view receives frequent updates asinitialBufferchanges; after a user dismisses the keyboard, the next terminal-output update can refocus the hiddenEditTextand reopen/retain the IME, making it impossible to keep the terminal keyboard closed while output is arriving. [ Exceeded comment limit ]autoFocuscallsrequestKeyboardFocus()synchronously from the prop setter. Expo/RN creates the view and applies its initial props before inserting it into the window, soinputViewhas no window token whenshowSoftInput()runs; unlike the removed constructorpost { ... }path, this class has no attach-time retry. As a result, the defaultautoFocusterminal can mount focused but with the soft keyboard not shown until the user taps it. [ Exceeded comment limit ]colsandrows, even thoughfontSizechangesterminalCanvas.cellWidthPxandcellHeightPx. If a font-size change leaves the computed grid dimensions unchanged (for example while a dimension is clamped at 2/400/200, or for a small size change),nativeResizeis skipped and Ghostty retains the old pixel cell dimensions. Terminal pixel-size queries then report stalecols * cell_width/rows * cell_heightvalues until a later grid-count change. [ Out of scope (post-validation triage) ]parseThemeConfignever resetspaletteColorswhen the new config contains nopaletteentries. After a theme with a palette is applied, settingthemeConfigto an empty or palette-free config still passes the old palette tonativeSetTheme, so the terminal keeps colors that are absent from the current configuration instead of reverting to defaults. [ Out of scope (post-validation triage) ]apps/mobile/modules/t3-terminal/android/src/main/java/expo/modules/t3terminal/TerminalCanvasView.kt — 0 comments posted, 3 evaluated, 3 filtered
startWordSelection, even when a selection is already active. If the press lands on a blank cell,nativeSelectWordAtreturns false (ghostty_terminal_select_worddocuments no selectable word), andstartWordSelectiononly resetsselectionActivewithout callingclearSelection; the native selection therefore remains highlighted while the canvas believes no selection is active. Subsequent taps no longer clear it, leaving stale selection state (and potentially a stale action toolbar). [ Exceeded comment limit ]extendSelectionTocan shrink the word selected by the initial long-press. For a word spanning columns 0..4, pressing column 2 then moving to column 3 takes theelsebranch and sends(anchor=0, end=3)to the native delegate, dropping column 4; moving toward column 1 similarly drops column 0. The storedwordEnd*/wordStart*values are only used as the anchor, so the claimed word-granular selection is not preserved while dragging within the original word. [ Exceeded comment limit ]handleCenterYaddshandleRadius()), but handle drag coordinates are later converted withrowAt, which treats that below-cell position as the next terminal row. As a result, after grabbing a handle on any non-bottom-row selection, the first move at or near the visible handle is interpreted asrow + 1and changes the selection even before the finger has moved to another row; vertical handle dragging is consistently offset downward. [ Exceeded comment limit ]apps/mobile/src/features/showcase/ShowcaseCaptureCoordinator.tsx — 0 comments posted, 1 evaluated, 1 filtered
themeAppliedis computed fromthemeIds, which are initially resolved fromstoredPreferences ?? {}and therefore equal the default palette while the preference load is still pending; if the requested showcase theme is that default, it becomestrueeven when the persisted light/dark themes will later differ. Because this final effect does not also requireappearancePreferencesReady, a fast fixture hydration can write the native ready marker and let the harness capture the persisted theme instead of the requested one. [ Exceeded comment limit ]apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx — 0 comments posted, 1 evaluated, 1 filtered
branch.currentfrom the refs list, even though the flow explicitly notes that this cached flag can lag an out-of-band checkout. If another process switches the project after the list is loaded, the stale row can still saycurrent: true; selecting it skipsswitchRef, thenflow.selectBranchrecords that branch while the repository remains on a different branch. Use the live checkout state (or compare againstcurrentCheckoutBranchName) when deciding whether a local selection needs checkout. [ Exceeded comment limit ]apps/mobile/src/features/threads/NewTaskRouteScreen.tsx — 0 comments posted, 1 evaluated, 1 filtered
projects, the rows remain enabled andselectProjectawaitsreleaseShareReservationbefore navigating. A double-tap (or two taps during a slow release) starts two calls; both releases can succeed/idempotently no-op, and each then dispatchesStackActions.push("NewTaskDraft", ...), leaving duplicate draft routes for the same share and potentially running the import against the shared draft twice. Add a synchronous selection/in-flight guard or disable the rows while the release is pending. [ Exceeded comment limit ]apps/mobile/src/features/threads/threadListV2.ts — 0 comments posted, 1 evaluated, 1 filtered
latestUserMessageAtand thenupdatedAt, but settled recency is based onsettledAtfor explicit settles and the latest timestamp acrosslatestTurnactivity when auto-settled. Consequently, a thread explicitly settled now (or one whose agent turn completed after its last user message) can be ordered behind much older settled threads, so the recency tail shows the wrong history order. [ Exceeded comment limit ]apps/mobile/src/native/T3ComposerEditor.native.tsx — 0 comments posted, 2 evaluated, 2 filtered
DMSans-Regular) to the native view, but the corresponding Android setter maps every non-Mononame toTypeface.DEFAULTinstead of loading/using that family. Consequently both Android composer screens render in the system default font after this implementation replaces the React NativeTextInputpath. [ Out of scope (post-validation triage) ]ComposerEditorPropsexposesonSubmit, andThreadComposerpasseshandleSendto it, but this Android implementation never forwards that callback toNativeView(and the Android native view has no submit event prop). As a result, Command-Return/hardware-keyboard submission silently does nothing on Android even though the same component supports it on iOS. [ Exceeded comment limit ]apps/server/src/mcp/PreviewMidsceneService.ts — 0 comments posted, 2 evaluated, 2 filtered
AbortControlleris wired only to the MCP/Effect interruption signal;broker.invokehas no host-disconnect signal or callback wired into this service. If the desktop host disconnects while Midscene is awaiting a model response (between broker calls),run(agent, controller.signal)continues instead of terminating. When it later requests another browser operation, the broker may have failed over or the operation can keep running against a stale tab, violating the required host-disconnect cancellation boundary. [ Exceeded comment limit ]querycallback always supplies a usage object viausageFromCall(queryResult.usage). When Midscene returns noAIUsageInfo(allowed byPreviewMidsceneAgentHandle.query), this produces an all-zero usage value, soexecutenever uses itsagent.metrics()fallback. Queries therefore report zero tokens/calls even though the agent performed a model request, whileactandassertcorrectly fall back to metrics. [ Exceeded comment limit ]apps/server/src/provider/Layers/CodexAdapter.ts — 0 comments posted, 2 evaluated, 2 filtered
parentThreadIdis a provider thread identifier, but this is emitted asparentAgentId, which the client treats as a task/agent ID. Every child with aparentThreadIdis therefore classified as aworkflow_agentand grouped under that ID; because no task with the parent provider-thread ID is created, the child becomes an orphan/direct entry instead of a normal Codex child with the intended hierarchy and kind. [ Failed validation ]turn.status === "failed", the mapper emits onlytask.updatedwithstatus: "failed"and dropsturn.error(the Codex schema documents this field as populated on failed turns). The persisted task then shows a generic failure with no error detail in the Agents panel, losing the provider's actual failure reason. [ Exceeded comment limit ]apps/server/src/pullRequest/BitbucketPullRequestProvider.ts — 0 comments posted, 1 evaluated, 1 filtered
listChangeRequestsdropsinput.involvementandinput.viewerwhen calling Bitbucket's API, and the service does not apply involvement locally (it only applies the separate row filters). Consequently the Bitbucket "authored" and "reviewing" list modes return all pull requests for the selected state/query instead of only requests involving the signed-in user. [ Failed validation ]apps/server/src/resourceTelemetry/Model.ts — 0 comments posted, 4 evaluated, 4 filtered
startTimeMsatidentityKey, while a missing native process is synthesized withmetric.creationTimeMs; these timestamps are explicitly treated as the same process within a 2-second tolerance at lines420-423, butprocessIdentityKeyuses exact values here. If the native timestamp is1000and the desktop timestamp is1500, a temporary native omission changes the key from300:1000to300:1500, causing a false exit/start and resetting CPU/I/O deltas; when native data returns it flips back again. [ Exceeded comment limit ]preservePreviousRatesis applied to synthetic Electron processes during every desktop-only merge. When a process is absent from the native snapshot,processis synthesized from the current desktop metric, but if it existed ininput.previous, this branch returns the oldprevious.process.cpuPercentinstead of the metric's current CPU percentage. Thus a desktop update changing an Electron process from (for example) 10% to 80% still publishes 10% until the next native update, defeating the desktop telemetry refresh for native-invisible Electron processes. [ Exceeded comment limit ]cumulativeCpuSeconds, the synthetic process created at lines409-416can carry a fractional CPU time because the fallback calculation does not round its elapsed CPU increment. This value is copied unchanged into the returned process atcpuTimeMshere, although the contracts requireResourceTelemetryProcess.cpuTimeMsto be a non-negative integer. For example,12.34%over1_000 msproduces123.4, yielding an invalid telemetry snapshot when it is validated or encoded. [ Exceeded comment limit ]orderProcessTreeon every snapshot at this line, and that helper recursively visits each child. A sufficiently deep process chain can exceed Node's call-stack limit and throwRangeError: Maximum call stack size exceeded, causing telemetry refreshes to fail. The iterative depth calculation does not make this ordering traversal stack-safe. [ Out of scope (post-validation triage) ]apps/server/src/textGeneration/PiTextGeneration.ts — 0 comments posted, 4 evaluated, 4 filtered
runPiJsontreats the firstagent_endevent as generation completion, but the Pi event handling code distinguishes retryingagent_endevents (willRetry: true) from terminal completion and waits foragent_settled/a terminal event. When a provider transiently fails and Pi starts an automatic retry, this code immediately readsget_last_assistant_textfrom the incomplete attempt, commonly yielding empty/invalid output or stale text and failing the generation instead of allowing the retry to finish. [ Exceeded comment limit ]Effect.forkScoped, so itsStream.fromPubSubsubscription is established asynchronously and the PubSub has no replay buffer. A very fast Pi/OMP process can publishagent_endafter the prompt is accepted but before this child fiber subscribes; the event is then lost,Fiber.join(agentEnd)never completes, and the request waits until timeout instead of returning the generated result. [ Exceeded comment limit ]generateBranchNameandgenerateThreadTitle, the input attachments are reduced to metadata by the prompt builder, and thepromptcommand at line 165 sends onlymessage—it never reads or supplies the attachment bytes in Pi RPC'simagesfield. Thus image-only/UI requests are presented to Pi as filenames and sizes despite the prompt telling the model to use attached images, producing titles/branches without the user's screenshot context. [ Exceeded comment limit ]PI_TIMEOUT_MSguard wrapsFiber.join(agentEnd)after theset_model, optionalset_thinking_level, andpromptRPC requests have already completed. Thoseclient.request(...)calls have no timeout, so a Pi/OMP process that stays alive but stops replying (including during startup/model selection or prompt acknowledgement) leaves text generation and its request fiber blocked indefinitely instead of producing the advertised timeout error. [ Exceeded comment limit ]apps/web/src/components/ContextPanel.tsx — 0 comments posted, 2 evaluated, 2 filtered
threadContextfor every mounted server thread. The server implementation of this RPC rejects threads without an active provider session (session: nullor a stopped/expired session), but the panel has no guard for that state and therefore shows a request error instead of context for historical/completed supported-provider threads. Since the panel is offered based on provider kind rather than session activity, opening Context on an old thread reliably hits this path. [ Exceeded comment limit ]contextQuery.data.provideris the driver slug returned by the context RPC, so an OMP session supplies"omp"(and the built-in Pi driver supplies"piAgent"). Because this value is preferred overthread.session.providerNameat line 181 andformatProviderDisplayNamehas no mappings for either slug, the Provider cell rendersOmp/PiAgentinstead of the configured user-facing names such asOh My Pi/Piwhenever the context request succeeds. [ Out of scope (post-validation triage) ]apps/web/src/components/search/ProjectContentSearchDialog.tsx — 0 comments posted, 1 evaluated, 1 filtered
group.matches.length, butgroupsis built fromvisibleMatches(only the first 100 results). A file with more than 100 matches is therefore shown as having only 100 matches until the user scrolls far enough to load more, even though the search result set already contains the full count. Compute the badge from all matches for that path, or label it as a visible-count. [ Out of scope (post-validation triage) ]apps/web/src/components/settings/SettingsPanels.tsx — 0 comments posted, 1 evaluated, 1 filtered
discoverInstalledFonts()can resolve while the user is still typing. This branch then unmounts the input and mountsFontFamilyPickerwithselectedFamily={trimmed}, which is derived from the committedvalue, not the localdraft; any draft text (and its pending timer) is discarded without being committed. A user who types during the permission/enumeration request can therefore lose the font name they entered. [ Failed validation ]apps/web/src/components/settings/settingsSearch.ts — 0 comments posted, 3 evaluated, 3 filtered
environment-identificationis included in the search catalog for every environment, even though the corresponding settings row is conditionally omitted whenresolveEnvironmentIdentificationPillLabelreturnsnull(for example, the existing tests showLatestandAlphado so). Searching for it in those environments returns a result whoseappearancetarget contains no such setting. [ Out of scope (post-validation triage) ]font-smoothingis always returned bysearchSettings, but the matchingFontSmoothingRowreturnsnullunlessnavigator.platformis macOS. On Windows/Linux, searching for this title produces a result with nofont-smoothinganchor, so selecting it cannot reach the requested setting. [ Out of scope (post-validation triage) ]start-from-originresult is advertised unconditionally, but its actualSettingsRowis rendered only whensettings.defaultThreadEnvMode === "worktree"(otherwise the row does not exist). Clicking this result only targets the parentnew-threadsanchor, so in the default/local mode the search result lands on a section without the requested setting and gives no way to reach it. [ Exceeded comment limit ]apps/web/src/themePalette.ts — 0 comments posted, 1 evaluated, 1 filtered
canvasis parsed throughparseThemeRgbColor, which returns only the source RGB and discards its alpha. Alpha-bearing canvas values are supported by the editor, so a canvas such asrgb(0 0 0 / 50%)is actually rendered composited over the app background, but all derived foregrounds, surfaces, andcanvasIsDarkdecisions here treat it as opaque black. Editing any advanced family after saving such a palette can therefore produce incorrect contrast choices and unreadable/mismatched derived colors; the canvas must be composited against the real backdrop (or alpha handled consistently) before deriving colors. [ Exceeded comment limit ]native/resource-monitor/src/main.rs — 0 comments posted, 3 evaluated, 3 filtered
self.system.refresh_processes_specifics(ProcessesToUpdate::All, ...)requests command lines for every process, while the laterMAX_PROCESS_COMMAND_BYTEStruncation only applies when constructing selected output rows.sysinfotherefore retains the full command-line arguments for all processes inSystem; a local workload with many processes/large arguments can make the sidecar allocate far beyond its advertised 64 MiB history/16 KiB command bounds on every sample, causing severe memory pressure or monitor termination. [ Exceeded comment limit ]processes.sort_by_keyemits the snapshot in numeric PID order rather than the documented depth-first process-tree order. With a tree such as root100-> child200-> grandchild300and another child150, the emitted list is100, 150, 200, 300, so a consumer of the native snapshot cannot treat contiguous rows as complete subtrees and renderer collapse/expansion can attach rows to the wrong tree structure. Preserve the traversal order (or emit explicit depth information) instead of sorting by PID. [ Out of scope (post-validation triage) ]matches_external_identityfloors the Electron timestamp by comparing it to the whole-second native value, but the Electron publisher rounds creation times to milliseconds. If the real process start is in the final 0.5 ms of a second (for example native10_000ms and Electron10_000.0? specifically native10_000ms with rounded Electron11_000ms), the rounded value falls into the next bucket and this rejects the still-matching PID. That external root and all of its descendants are then omitted for the process lifetime. [ Out of scope (post-validation triage) ]packages/shared/src/usageMerge.ts — 0 comments posted, 1 evaluated, 1 filtered
bucket.costSource === "providerReported"as proof that every record in the bucket was provider-reported.UsageAggregatorcan put provider-reported and model-priced (or unpriced) records for the same provider/model/day into one bucket and marks such a mixed bucketmodelPriced, while retainingunpricedRecords. ConsequentlyproviderReportedShareis understated (often zero) andmodelPricedShareis overstated, so the UI reports incorrect cost-quality provenance for mixed buckets. [ Exceeded comment limit ]