fix(aws): Endpoint.none to avoid deadlock - #1252
Conversation
`getAccountId` runs an STS `GetCallerIdentity` from inside the construction of `AWSEnvironment`, and passed `Layer.empty` for the endpoint when there was no custom one. That let the call fall through to the ambient `Endpoint.fromEnvironment`, which reads the endpoint off `AWSEnvironment` — the `Effect.cached` resolution already in flight on the same fiber. The fiber then awaits its own cache deferred forever: no timer, no socket, event loop idle, zero output, and the cross-process auth lock held for the whole hang (so the next run wedges for 120s on the lock timeout). Provide `Endpoint.none` instead, the same guard the neighbouring `Region.of(region)` already applies for the identical cycle. Fixes #1231
|
Install the packages built from this commit: Alchemyalchemy bun add https://pkg.ing/alchemy/c5fe43a@alchemy.run/better-auth bun add https://pkg.ing/@alchemy.run/better-auth/c5fe43a@alchemy.run/cloudflare-runtime bun add https://pkg.ing/@alchemy.run/cloudflare-runtime/c5fe43a@alchemy.run/frontend-frameworks bun add https://pkg.ing/@alchemy.run/frontend-frameworks/c5fe43a@alchemy.run/node-utils bun add https://pkg.ing/@alchemy.run/node-utils/c5fe43a@alchemy.run/pr-package bun add https://pkg.ing/@alchemy.run/pr-package/c5fe43a@alchemy.run/floci bun add https://pkg.ing/@alchemy.run/floci/c5fe43aDistilled@distilled.cloud/core bun add https://pkg.ing/@distilled.cloud/core/dd2a322@distilled.cloud/aws bun add https://pkg.ing/@distilled.cloud/aws/dd2a322@distilled.cloud/axiom bun add https://pkg.ing/@distilled.cloud/axiom/dd2a322@distilled.cloud/cloudflare bun add https://pkg.ing/@distilled.cloud/cloudflare/dd2a322@distilled.cloud/hetzner bun add https://pkg.ing/@distilled.cloud/hetzner/dd2a322@distilled.cloud/neon bun add https://pkg.ing/@distilled.cloud/neon/dd2a322@distilled.cloud/planetscale bun add https://pkg.ing/@distilled.cloud/planetscale/dd2a322 |
A deploy that blocked during credential resolution produced no output at all — not at any log level, and not even in `.alchemy/log/out`, because nothing on the `deploy -> state_store.init -> auth lock` path logged anything. That is the second half of #1231: the deadlock was diagnosable only with a sampling profiler. - Log the state-store init and each auth-lock acquire/acquired/release at debug, so the phase a run is stuck in is visible. - Print a stall notice naming the operation and the lock path every 30s while a lock is being waited on or held. It goes through `Console.error` rather than the logger on purpose: with telemetry on, `TelemetryLive` replaces the console logger with the OTLP one, so log records never reach the terminal of the user watching it hang. Suppressed for `login`/`configure`, which legitimately block on the user and have a prompt on screen that must not be trampled. - Honour `--log-level` on the console. With telemetry on every record went to `.alchemy/log/out` and nothing to stderr, so `--log-level debug` changed nothing a user could see. Add a console sink when the flag is explicitly passed; with telemetry off the default logger is still in the set, so nothing double-prints. Deliberately not a hard timeout on the locked effect: a slow-but-real SSO refresh or OAuth round-trip would be killed by one. The notice makes the stall visible without deciding it is fatal.
|
Confirming this is still needed against the current published release, and adding an independent verification of the fix.
One detail from the beta.72 to beta.74 window is worth naming, because it makes the line look already handled. beta.72's Independent verification, arrived at separately before finding this PR:
Two notes for anyone working around this before the merge:
Also relevant: #1106 describes the same self-await and proposes the same one-line fix, so this PR should close that issue too. It is currently linked only to #1231. |
|
This is still reproducible on When no custom endpoint is configured, which is the default for anyone deploying to real AWS, What makes this worth merging rather than working around: the comment two lines above the same call // Provide Region directly from the resolved inputs. Relying on the
// ambient Region provider (Region.fromEnvironment) here would
// deadlock: it derives the region from AWSEnvironment, which is the
// very service still being constructed by this STS call.
Region.of(region),
The symptom is bad out of proportion to the fix. There is no error, no timeout and no network We have been carrying this exact change as a We also confirmed the deadlock is not a Measured in our repository against the real Could this be merged? It is a two-line fix for a failure mode that is silent, total, and hits the One small note on shape, take it or leave it: |
Closes #1231.
alchemy deployon an AWS stack could hang forever with zero output and no network activity. It is a self-deadlock, not credentials, S3, or the auth lock.getAccountIdruns STSGetCallerIdentityfrom inside the construction ofAWSEnvironment. It already guardsRegionagainst the resulting cycle, but passedLayer.emptyfor the endpoint:Layer.emptylets the call fall through to the ambientEndpoint.fromEnvironment— provideMerge'd by bothAWS.state()andAWS.providers()— whose service Effect reads the endpoint offAWSEnvironment:That re-enters the
Effect.cachedAWSEnvironmentresolution already in flight on the same fiber, so the fiber awaits its own cache deferred forever: no timer, no socket, event loop idle inkevent. The cross-process auth lock is held for the whole hang, which is why the next run reportsTimed out waiting for the alchemy auth lock … held for over 120s.Trigger
method: "env"credentials (what exportedAWS_*gives you) with noAWS_ACCOUNT_ID(so STS is needed) and noAWS_ENDPOINT_URL(soLayer.emptyis taken). Thestored-without-accountIdbackfill path hits it too. Not state-store specific —Endpoint.fromEnvironmentis inAWS.providers()as well.Verification
Probe stack with
AWS.state(), profile configured{ method: "env" }, dummyAWS_*credentials:AWS_ENDPOINT_URLThe security token included in the request is invalidAWS_ENDPOINT_URLsetEndpoint.ofbranch)The silence (issue's second ask)
A stalled deploy printed nothing at any log level, and
.alchemy/log/outwas empty too — nothing on thedeploy → state_store.init → auth lockpath logged at all.Via
Console.error, not the logger — with telemetry on,TelemetryLivereplaces the console logger with the OTLP one, so log records never reach the terminal of the user watching it hang. Suppressed forlogin/configure, which legitimately block on the user and have a prompt on screen.--log-levelnow reaches the console. Telemetry-on runs sent every record to.alchemy/log/outand nothing to stderr, so--log-level debugchanged nothing visible. A console sink is added when the flag is explicitly passed; with telemetry off the default logger is still in the set, so nothing double-prints.No hard timeout on the locked effect: a slow-but-real SSO refresh or OAuth round-trip would be killed by one. The notice makes a stall visible without declaring it fatal.
Before / after on the same stalling run, telemetry on, no flags: