2.0.0-beta.77 - Profiles, CLI & Workers Tracing
beta.77 redesigns the CLI. alchemy login becomes
alchemy profile, a dashboard for every connected account
whose every action is also a non-interactive subcommand; profiles
are stored as a folder per profile with a file per provider
instead of a central profiles.json; and alchemy dev gets a
plan/output widget you can flip with ←/→. Outside the CLI,
Cloudflare.Telemetry() sends Effect spans to Workers
Observability with no OTLP setup, Workflows take cron schedules,
and AWS gains provisioned ElastiCache.
Here is the new CLI on a fresh machine, start to finish:
alchemy profile → alchemy dev → alchemy deploy → alchemy drift → alchemy destroy. The same walkthrough, frame by frame, is below.
1. Connect an account
Section titled “1. Connect an account”alchemy login is now alchemy profile
(#1234,
#1235,
#1547). On a
fresh machine it opens on an empty default profile:

e lists every provider. Space queues a change on the focused
row — here Cloudflare goes to add — and Enter runs the queue:

Each provider asks only what it needs. Cloudflare offers OAuth or a token:

OAuth opens the browser; you approve the grant there:

Back on the dashboard the provider is a row with its live details:

Profiles are tabs, ←/→ switches between them, and every profile
holds one connection per provider — a work profile with a
Cloudflare token next to a default with OAuth + AWS SSO:

Every key on the dashboard is also a subcommand, and the subcommands never prompt:
alchemy profile create workalchemy profile edit --profile work --add cloudflare # runs the OAuth flowalchemy profile refresh --provider cloudflarealchemy profile rename work stagingalchemy profile delete staging --yesFor agents and CI, token-shaped methods are configured entirely
from flags. --set takes a literal, an environment variable, or
stdin, so secrets never touch shell history:
alchemy profile edit --add cloudflare --method stored \ --set apiToken=env:CLOUDFLARE_API_TOKEN \ --set accountId=0123456789abcdef0123456789abcdef
op read op://vault/neon/key | alchemy profile edit --add neon --method stored --set apiKey=-Under the hood, the central profiles.json and the separate
credentials/ tree are gone. A profile is a directory and each
provider is one file holding its method, config, and credentials
together. Existing installs migrate on first run, with the old
layout backed up to ~/.alchemy/.profiles-v0-<timestamp>/
(#1429):
~/.alchemy/profiles/├─ default/│ ├─ cloudflare.json # method: oauth, accountId, tokens, scopes│ └─ aws.json # method: sso, ssoProfile└─ work/ └─ cloudflare.json # method: stored, apiToken, accountIdOnly profile commands log in. deploy, plan, and dev never
open a wizard — a missing credential fails with the exact
alchemy profile edit --add … to run. Explicit provider
environment variables (CLOUDFLARE_API_TOKEN, AWS_ACCESS_KEY_ID,
…) take precedence over the profile, per provider, and the CLI
logs which ones it used
(#1520).
Docs: profile · Profiles · CI.
2. alchemy dev
Section titled “2. alchemy dev”The stack in the video is a Worker bound to a KV namespace and an R2 bucket:
export default class Api extends Cloudflare.Worker<Api>()( "Api", { main: import.meta.url }, Effect.gen(function* () { const visits = yield* Cloudflare.KV.ReadWriteNamespace(Visits); const uploads = yield* Cloudflare.R2.ReadWriteBucket(Uploads); return { fetch: /* … */ }; }).pipe( Effect.provide([ Cloudflare.KV.ReadWriteNamespaceBinding, Cloudflare.R2.ReadWriteBucketBinding, ]), ),) {}alchemy dev emulates all three locally. Logs scroll above; the
widget at the bottom shows the stack’s output:

←/→ flips the widget to the last applied plan — the same
component deploy, drift, and destroy render
(#1458,
#1492). ↑/↓
scrolls it, p hides it:

Save a file and only the affected resource restarts. Reloads follow
the import graph from your entrypoint — sibling infra/ and src/
trees included — and only fire when a file’s contents actually
change (#1461,
#1477):

3. alchemy deploy
Section titled “3. alchemy deploy”deploy shows the plan first. Bindings appear under the resource
they attach to, and the prompt defaults to go:

Provider logs stream while it runs; the plan ticks over as each resource lands and the stack’s outputs print at the end:

$ curl -s https://demo-api-live-samgoodwin-sthgco4pjlba35wc.testing-2b2.workers.dev{"message":"Hello from Alchemy","visits":1}4. alchemy drift
Section titled “4. alchemy drift”Someone renames the KV namespace in the Cloudflare dashboard.
alchemy drift re-reads the cloud and diffs it against what was
deployed — the changed attribute renders inline, and Repair
puts it back:


alchemy drift --repair does the same without the prompt.
5. alchemy destroy
Section titled “5. alchemy destroy”Destructive prompts default to Cancel:


Without a TTY
Section titled “Without a TTY”In CI, a pipe, or a coding agent, the same commands print plain
append-only lines instead of the TUI — or force it with
ALCHEMY_PLAIN=1 / --no-input
(#1423,
#1426). Same
deploy as above, plain:

Exit codes are 0 done / 1 failed or declined / 130 cancelled,
and every run writes a debug log under ~/.alchemy/logs — the first
thing to attach to a bug report.
Docs: CLI · dev · deploy · drift.
Cloudflare.Telemetry()
Section titled “Cloudflare.Telemetry()”Effect spans from a Worker now land in
Workers Observability
(#1444).
Provide the Layer on the Worker and your Effect.withSpan frames
show up in the Cloudflare dashboard, nested under the platform’s
own spans:
export default Cloudflare.Worker( "Api", { main: import.meta.url }, Effect.gen(function* () { return { fetch: Effect.gen(function* () { const user = yield* kv.get("user:1").pipe(Effect.withSpan("load.user")); return yield* respond(user); }).pipe(Effect.withSpan("handle")), }; }).pipe(Effect.provide(Cloudflare.Telemetry())),);http.server GET└─ handle ├─ load.user │ └─ kv_get └─ respondCloudflare owns sampling and export, so there is no OTLP endpoint
and nothing to flush. It composes with Axiom.Telemetry for the
same waterfall in both. The Worker’s compatibility date must be
2026-07-28 or later.
Docs: Native Workers tracing.
Workflows: cron schedules
Section titled “Workflows: cron schedules”Cloudflare Workflows take wrangler-compatible schedules, so a
cron expression creates instances natively — no Cron Trigger
Worker calling workflow.create()
(#1491):
export default class Hourly extends Cloudflare.Workflow<Hourly>()( "Hourly", { schedules: ["0 * * * *"] }, Effect.gen(function* () { return Effect.fn(function* () { const event = yield* Cloudflare.Workflows.WorkflowEvent; return event.schedule?.cron; }); }),) {}Provisioned ElastiCache
Section titled “Provisioned ElastiCache”AWS.ElastiCache adds SubnetGroup, ReplicationGroup (Valkey
and Redis OSS) and CacheCluster (Memcached) alongside the
serverless cache, with Connect* bindings that publish the
endpoint to a Lambda and attach it to the cache’s VPC
(#1422).
Thanks Henning Pokriefke!
const cache = yield* AWS.ElastiCache.ReplicationGroup("Cache", { engine: "valkey", nodeType: "cache.t4g.micro", subnetGroupName: subnetGroup.subnetGroupName, securityGroupIds: [sg.groupId], replicasPerNodeGroup: 1, automaticFailoverEnabled: true,});
// inside the Lambda: endpoint env + VPC attachmentconst conn = yield* AWS.ElastiCache.ConnectReplicationGroup(cache, { subnetIds: vpc.privateSubnetIds, securityGroupIds: [sg.groupId],});Railway: private services, pre-deploy, local contexts
Section titled “Railway: private services, pre-deploy, local contexts”Three additions to Railway.Service, all from
Dallen Pyrah — thanks!
(#1439,
#1438,
#1440)
const worker = yield* Railway.Service("Worker", { project, context: "./worker", // build from a local directory preDeploy: { command: "bun migrate" }, // runs between build and start publicDomain: false, // {name}.railway.internal only});Also in this release
Section titled “Also in this release”- Fly health checks
(#1463) —
services[].checksonFly.MachineandFly.Service. Thanks Adam Svystun! Fly Machines requests also stop hitting/v1/v1/…(#1543). - Durable Objects:
webSocketErrorand caller context (#1497, #1498) —blockConcurrencyWhile/storage.transactionrun with the calling fiber’s context. Thanks Alex!state.abort(reason, { retryAlarm: false })stops an interrupted alarm from retrying (#1374). Cloudflare.Worker.reftypechecks (#1434). Thanks pollux!- Asset uploads run three buckets at a time
(#1445),
matching wrangler; workerd bumped to
20260901.1(#1442). Thanks Rahul Mishra! - Workflow steps with
retriesbut notimeoutno longer crash (#1416). Thanks Lord Coughmann! - Ctrl+C twice force-quits a slow shutdown (#1427).
_headers/_redirectsno longer crash local dev (#1418). Thanks Michael!- Worker Loader
get()returns a usable stub (#1488) andRpcWorkerkeeps its logical ID (#1487). nukeshows progress again (#1532), andprovider cloudflare bootstraphonors--profile(#1538).- PR preview packages live as long as the PR (#1511). Thanks Michael K!
- Docs rewritten as a narrative (#1470, #1472) — Getting started, What is Alchemy, and the Workers hub read top to bottom.