Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions patches/@alchemy.run__cloudflare@55e34d0272.patch
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
diff --git a/lib/Containers/ContainerProvider.js b/lib/Containers/ContainerProvider.js
index 4c610bb..752c583 100644
--- a/lib/Containers/ContainerProvider.js
+++ b/lib/Containers/ContainerProvider.js
@@ -6,7 +6,7 @@ import * as Containers from "@distilled.cloud/cloudflare/containers";
import { Unowned, Unresolved, UnresolvedIdentity } from "alchemy/AdoptPolicy";
import { AlchemyContext } from "alchemy/AlchemyContext";
import { getStableContextDir } from "alchemy/Bundle/TempRoot";
-import { deepEqual, isResolved } from "alchemy/Diff";
+import { deepEqual, isResolved, stripEffects } from "alchemy/Diff";
import * as Provider from "alchemy/Provider";
import {} from "alchemy/Resource";
import { sha256Object } from "alchemy/Util/sha256";
@@ -865,7 +865,12 @@ export const LiveContainerProvider = () => Provider.effect(ContainerPlatform, Ef
};
return ContainerPlatform.Provider.of({
stables: ["accountId", "applicationId"],
- diff: Effect.fn(function* ({ id, olds = {}, news = {}, output, newBindings, oldBindings }) {
+ diff: Effect.fn(function* ({ id, olds = {}, news: desired = {}, output, newBindings, oldBindings, }) {
+ // An Effect-native (`main`) Container's props carry its program as
+ // an Effect in `exports`, which `isResolved` reads as unresolved.
+ // Without stripping it, diff always returns early and a changed
+ // bundle never reaches the image hash comparison.
+ const news = stripEffects(desired);
if (!isResolved(news))
return;
yield* validateContainerConfiguration(news, output?.schedulingPolicy);
@@ -1334,4 +1339,4 @@ const toAttributes = (application) => {
dev: undefined,
};
};
-//# sourceMappingURL=ContainerProvider.js.map
\ No newline at end of file
+//# sourceMappingURL=ContainerProvider.js.map
diff --git a/lib/Containers/ContainerPublication.js b/lib/Containers/ContainerPublication.js
index ee528bd3c15e598d03c467bfd8b0bf905fa7a2b4..cecaaf3300946abf67c8a3dfbca5a62b95f0e681 100644
--- a/lib/Containers/ContainerPublication.js
Expand Down Expand Up @@ -54,6 +88,40 @@ index d05bcee55db9274620a542609dfb5ca4954d8051..8248f446d3a1edb7ed50e1ea67e0cae6
// export class proxy stubs for Durable Objects and Workflows
${[
...(hasDoClasses
diff --git a/src/Containers/ContainerProvider.ts b/src/Containers/ContainerProvider.ts
index 0bdeeec..12931b2 100644
--- a/src/Containers/ContainerProvider.ts
+++ b/src/Containers/ContainerProvider.ts
@@ -6,7 +6,7 @@ import * as Containers from "@distilled.cloud/cloudflare/containers";
import { Unowned, Unresolved, UnresolvedIdentity } from "alchemy/AdoptPolicy";
import { AlchemyContext } from "alchemy/AlchemyContext";
import { getStableContextDir } from "alchemy/Bundle/TempRoot";
-import { deepEqual, isResolved } from "alchemy/Diff";
+import { deepEqual, isResolved, stripEffects } from "alchemy/Diff";
import * as Provider from "alchemy/Provider";
import type { ScopedPlanStatusSession } from "alchemy/Report";
import { type ResourceBinding } from "alchemy/Resource";
@@ -1266,7 +1266,19 @@ export const LiveContainerProvider = () =>

return ContainerPlatform.Provider.of({
stables: ["accountId", "applicationId"],
- diff: Effect.fn(function* ({ id, olds = {}, news = {}, output, newBindings, oldBindings }) {
+ diff: Effect.fn(function* ({
+ id,
+ olds = {},
+ news: desired = {},
+ output,
+ newBindings,
+ oldBindings,
+ }) {
+ // An Effect-native (`main`) Container's props carry its program as
+ // an Effect in `exports`, which `isResolved` reads as unresolved.
+ // Without stripping it, diff always returns early and a changed
+ // bundle never reaches the image hash comparison.
+ const news = stripEffects(desired);
if (!isResolved(news)) return;
yield* validateContainerConfiguration(news, output?.schedulingPolicy);
if (!isResolved(newBindings)) return;
diff --git a/src/Containers/ContainerPublication.ts b/src/Containers/ContainerPublication.ts
index b8fe861b516976252b27f843da01fcbfac58e4ec..04d8f1e598ed150e79f1285767eb05b9a87f97be 100644
--- a/src/Containers/ContainerPublication.ts
Expand Down
6 changes: 6 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Upstream builds container images with linked bundle copies and a three-minute ti

Remove this part of the patch when upstream bounds timeout retries.

## Alchemy container change detection

An Effect-native container (one with `main`) keeps its program as an Effect in `props.exports`. The container provider's `diff` returns early when `isResolved(news)` is false, and that Effect always makes it false. The engine then compares props without Effects, finds them equal, and plans `noop`. So a change to guest code alone never rebuilt the chat or PR review sandbox image: both stayed on the image from 2026-10-28 until this patch. The Cloudflare patch runs `stripEffects` on `news` before that check, as the Worker provider already does, so the bundle hash reaches the image comparison. It covers both TypeScript and shipped JavaScript.

Remove this part of the patch when upstream strips Effects in the container `diff`.

## Effect snapshot

`@effect/platform-cloudflare` is not published yet, so every Effect package comes from a pkg.pr.new snapshot of Effect PR 7322 (`eff-698-cloudflare-cluster`). The pinned commit `111d7cfdd4fb15079fcb0b56b98d2290e40cd5b8` is the head of `eff-698-cloudflare-cluster-4.1.2`: the PR branch as of `2646b53` (the earlier `eff-698-cloudflare-cluster-4.0.7` pin) rebased onto Effect `4.1.2`, plus one commit adapting it to 4.1. The snapshot packages report version `4.1.2`, which satisfies Alchemy's and Foldkit's `4.1` peer ranges. Sharing one snapshot keeps janitor and clanka on the same Effect build. To move forward, rebase the branch onto the next release, let the Effect `Snapshot` workflow publish the new head, and update every catalog URL. Switch to registry packages once the PR is released. The root catalog and overrides keep every Effect package on one version, including `@effect/sql-d1` and `@effect/sql-sqlite-do`, which Alchemy depends on.
Expand Down
16 changes: 8 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.