Skip to content

feat(cli): add supabase workers push - #6262

Open
johnstonmatt wants to merge 1 commit into
FUNC-753/workers-newfrom
FUNC-753/workers-push
Open

feat(cli): add supabase workers push#6262
johnstonmatt wants to merge 1 commit into
FUNC-753/workers-newfrom
FUNC-753/workers-push

Conversation

@johnstonmatt

@johnstonmatt johnstonmatt commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds supabase workers push (aliased deploy) and the machinery it needs:

  • workers-api.ts — the typed Workers Management API client.
  • tar.ts / worker-package.ts — packaging a worker directory into the build
    context that gets uploaded.
  • worker-classify.ts — best-effort runtime detection from marker files, so a
    directory with no [workers.<name>] runtime can still deploy. The guess is
    always reported with a nudge to pin it down, never applied silently.

Stack 3 of 4, on top of workers new (#6261).

Linked issue

FUNC-753 (Linear). Supabase maintainer, exempt from the open-for-contribution flow.

Checklist

@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch 2 times, most recently from aa0ea27 to fa9be15 Compare August 20, 2026 10:19
@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch from fa9be15 to 959520b Compare August 20, 2026 13:13
@johnstonmatt
johnstonmatt marked this pull request as ready for review August 20, 2026 13:27
@johnstonmatt
johnstonmatt requested a review from a team as a code owner August 20, 2026 13:27
@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch from 959520b to d38a32b Compare August 20, 2026 13:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 959520b26b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/shared/workers/worker-package.ts Outdated
Comment thread apps/cli/src/shared/workers/worker-config.ts
Comment thread apps/cli/src/shared/workers/workers-api.ts
Comment thread apps/cli/src/legacy/commands/workers/push/push.command.ts
Comment thread apps/cli/src/shared/workers/workers-api.ts
Comment thread apps/cli/src/shared/workers/worker-package.ts
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts Outdated
Comment thread apps/cli/src/shared/workers/workers.errors.ts
Comment thread apps/cli/src/legacy/commands/workers/push/SIDE_EFFECTS.md Outdated
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts
Comment thread apps/cli/src/shared/workers/worker-package.ts Outdated
Comment thread apps/cli/src/shared/workers/workers-api.ts
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@905115bc4f64b3ce876f9915d451a3182088c1a5

Preview package for commit 905115b.

@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch from d38a32b to e7acc02 Compare August 21, 2026 08:05

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7acc025f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/shared/workers/worker-package.ts Outdated
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/workers/push/push.handler.ts Outdated
johnstonmatt added a commit that referenced this pull request Aug 21, 2026
… the upload URL

Addresses the review findings on #6262.

Two failures happened only after the remote project had already changed. `-o env`
cannot encode the payload's `workers` array, but that was discovered at emit
time — after every upload, deploy and build poll — so the command exited
non-zero having deployed, inviting a retry that deployed again. And an absent
optional `image_version` was left in the payload as `undefined`, which
smol-toml cannot represent, so `-o toml` threw at the same point. Both are now
settled before the first request: `legacyRejectUnsupportedWorkersOutput` runs up
front, and the field is spread conditionally like `url` beside it.

Packaging silently tolerated a filesystem it could not read. An unreadable file
was archived as zero bytes and an unreadable directory dropped its whole subtree,
so `push` reported success for an image built from an application with a hole in
it. Both propagate now. The redundant `Number()` around `File.Info.mode` is gone
too — it is a plain number, and wrapping it invited the reading that it was an
`Option`.

The presigned upload URL was reaching the `--debug` log. Its query string is a
write-capable credential for the archive a deploy is about to build from, so it
does not belong in terminal scrollback or a CI log. Fixed at the logging
boundary rather than by giving the upload its own HTTP client: redaction in
`legacyHttpClientLayer` keeps the client injectable for tests and covers every
presigned URL the CLI might ever log, not just this one.

Also: the build-poll read retries on a wall-clock budget instead of three
back-to-back attempts, which a two-second blip exhausted while the surrounding
poll still had minutes left; `WorkersApiUnexpectedStatusError` classifies from
the status it carries, so a 401 reads as "log in" rather than as a service
failure across every Workers endpoint; a source of nothing but empty directories
is refused before an upload slot is minted, instead of deploying an image with no
handler; the runtime guess is only reported once the source is known to exist;
and `config.toml` loading moved inside the finalizers, so a malformed config
still flushes telemetry — matching `config/push`, which already had it right.

Two findings are deliberately left for follow-ups: streaming the build context
rather than buffering it, and an ignore mechanism so `.env` and `.git` can be
kept out of the uploaded archive. Both are new work rather than defect fixes.
The `SIDE_EFFECTS.md` also now records the telemetry and linked-project writes,
the cache-miss API route, and why there is no live suite yet.
Comment thread apps/cli/src/shared/workers/worker-package.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38fd3f44bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/shared/workers/worker-package.ts Outdated
Comment thread apps/cli/src/legacy/commands/workers/push/push.integration.test.ts
Comment thread apps/cli/src/legacy/commands/workers/push/push.command.ts
Comment thread apps/cli/src/shared/workers/worker-package.ts Outdated
johnstonmatt added a commit that referenced this pull request Aug 21, 2026
…on a clock

Four follow-ups on #6262, three of them corners the previous round left open.

`fs.stat` was the last swallowed filesystem error in `collectEntries`. An entry
that disappears between `readDirectory` and the stat, or whose metadata cannot be
read, was dropped from the archive — the same "deploy an application with a hole
in it" outcome as the reads that were fixed, just losing a whole entry instead of
its contents.

`createTar` throws `TarPathTooLongError` for a name USTAR cannot represent, and
calling it directly inside the generator made that a defect rather than a typed
failure. `withJsonErrorHandling` only catches the failure channel, so
`--output-format json` would have terminated with no structured error payload for
an error the class explicitly declares as user-actionable. Wrapped in
`Effect.try`, narrowing to that class and letting anything else stay a defect —
because anything else there really is a bug.

The transient-failure integration test was waiting on the real clock. The outer
poll schedule was injected but the per-read retry was not, and that one is spaced
in seconds, so a 500-then-200 sequence sat for two seconds and more transient
responses would have pushed it toward thirty. Both schedules are parameters now;
that test went from ~2s to 22ms.

`--instances` is bounded at the parser, the way `[workers.<name>] instances` is
bounded in the config schema. `--instances=-1` was accepted by the integer flag
and carried into the deploy spec, so an impossible scaling request reached the
remote endpoint only after the build context had been packaged and uploaded.
`Flag.filter` refuses it during parsing, consistent with how `--runtime` is
validated.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eec654d4ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const requested =
flags.names.length > 0
? yield* Effect.forEach(flags.names, legacyValidateWorkerName)
: yield* legacyDiscoverWorkerNames(project);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate failures while discovering bulk-deploy workers

When workers push is invoked without names and the workers root cannot be listed, or metadata for one entry cannot be read, legacyDiscoverWorkerNames in workers.shared.ts:122-128 converts those failures to []/None. This new call can therefore deploy only the config-listed or remaining workers and exit successfully while silently omitting directory-only workers, contrary to the promise to deploy every worker; preserve discovery failures in the Effect failure channel instead of treating them as absence.

AGENTS.md reference: AGENTS.md:L64-L68

Useful? React with 👍 / 👎.

> here would be permanently skipped or permanently red. Revisit when the v2
> Workers routes are available on that stack.

## Files Read

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document project resolution and credential side effects

After the prior cache and telemetry additions, the compatibility document still omits side effects performed by the resolver and auth layers: legacy-project-ref.layer.ts:87-95 consumes SUPABASE_PROJECT_ID, reads <workdir>/supabase/.temp/project-ref, and may call GET /v1/projects for interactive selection, while legacy-credentials.layer.ts:403-443 reads the profile and legacy keyring entries or <SUPABASE_HOME>/access-token. These happen before the Workers requests and leave the Files Read, API Routes, and Environment Variables sections incomplete.

AGENTS.md reference: apps/cli/AGENTS.md:L359-L366

Useful? React with 👍 / 👎.

const info = yield* fs.stat(absolutePath);

const modified = info.mtime;
const mtime = modified._tag === "Some" ? Math.floor(modified.value.getTime() / 1000) : 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize pre-epoch mtimes before writing octal headers

When a source file has a filesystem mtime before the Unix epoch, this calculation produces a negative value, and writeOctal later serializes it as a field such as 000000000-1. That is not a valid USTAR octal timestamp—GNU tar reports a malformed numeric time and exits unsuccessfully—so an otherwise valid source tree can produce a build context the server cannot extract; clamp pre-epoch timestamps to 0, as already done for absent mtimes, or encode signed values correctly.

Useful? React with 👍 / 👎.

return yield* Effect.fail(
new WorkerBuildTimeoutError({
detail: `"${name}" was still building when this command stopped waiting.`,
suggestion: `Check on it with \`supabase workers status ${name}\`.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point build timeouts at an available status path

When a build exceeds the polling budget, this recovery instruction tells the user to run supabase workers status <name>, but a repository-wide search shows that the Workers command registers only new and push; there is no workers status handler or alias in this commit. The user therefore waits ten minutes and is then directed to a command that only returns an unknown-command error, so the suggestion should point to an available dashboard/API path or the status command should ship with this workflow.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 715ed177bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return yield* Effect.fail(
new WorkerSourceMissingError({
detail: `${sourceDisplay} is empty, so there is nothing to deploy.`,
suggestion: `Add your code there, or re-scaffold it with \`supabase workers new ${name} --force\`.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point empty-source recovery at a valid command

When the source directory is empty, this recovery instruction recommends supabase workers new <name> --force, but new.command.ts no longer defines a force flag and the existing directory would prevent a normal re-scaffold. Following the suggestion therefore produces an unknown-option error instead of repairing the worker; either describe removing the empty directory before rerunning new, or provide another currently supported recovery path. The same stale suggestion also appears in the empty-subdirectory branch.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49ee4a9a73

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


for (const marker of MARKERS) {
for (const file of marker.files) {
const found = yield* fs.exists(join(dir, file)).pipe(Effect.orElseSucceed(() => false));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate marker lookup failures before choosing a runtime

When a worker has no configured runtime and an fs.exists call fails transiently or because the marker cannot be inspected, this converts the failure to false and continues classification. If the subsequent package walk succeeds, a Node or Dockerfile worker can be uploaded and deployed as the default Deno runtime instead of failing before remote side effects; preserve the filesystem failure in the Effect channel rather than treating it as marker absence.

AGENTS.md reference: AGENTS.md:L64-L68

Useful? React with 👍 / 👎.

const archive = yield* Effect.try({
try: () => gzipSync(createTar(entries)),
catch: (cause) => {
if (cause instanceof TarPathTooLongError) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep oversized tar fields in the failure channel

When a source entry has an mtime beyond USTAR's 11-digit octal limit (for example, a filesystem timestamp after roughly 2242), createTar throws the explicitly user-actionable TarFieldTooLargeError, but this catch only returns TarPathTooLongError and rethrows the former as a defect. Consequently --output-format json and stream-json terminate without their structured error payload; return both declared tar-limit errors through the typed failure channel.

AGENTS.md reference: apps/cli/AGENTS.md:L399-L409

Useful? React with 👍 / 👎.

const spec: WorkerDeploySpec = {
// A plain Dockerfile build has no catalog runtime to name; the uploaded
// context carries its own Dockerfile and is built as-is.
...(runtime === "dockerfile" ? {} : { runtime }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate Dockerfile workers before uploading

When a worker is explicitly configured with runtime = "dockerfile" but its nonempty source has no usable top-level Dockerfile, this omits the catalog runtime and deploys a context that the server cannot build. The CLI already has all of the source metadata before minting the upload slot, so reject a missing or non-file Dockerfile locally rather than uploading, starting a deployment, and waiting for the inevitable remote build failure.

Useful? React with 👍 / 👎.

contents: new Uint8Array(0),
mode: 0o777,
mtime: 0,
linkTarget: linkTarget.value,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle pnpm links that escape the build context

When a worker source is a pnpm workspace package, dependency links under its node_modules commonly target the repository-level node_modules/.pnpm directory via paths such as ../../../node_modules/.pnpm/.... This archives only the symlink while the target lies outside the worker source and is therefore absent from the uploaded context, so the remote catalog build either rejects the link or produces an image with missing dependencies. Materialize confined external targets into the archive or reject these links before upload with actionable guidance.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86d0f6bd28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// An empty directory packages and deploys perfectly happily, producing an
// image with nothing in it — a success message for a worker that cannot
// serve anything. Refuse before uploading rather than after.
const contents = yield* fs.readDirectory(worker.sourceDir).pipe(Effect.orElseSucceed(() => []));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve source-directory read failures

When the source directory exists but cannot be listed because of permissions or an I/O error, this fallback converts the filesystem failure to an empty entry list. The command then incorrectly reports that the source is empty and offers source-editing remediation instead of exposing the actual filesystem error; preserve the PlatformError and only treat a successful empty listing as empty.

AGENTS.md reference: AGENTS.md:L64-L68

Useful? React with 👍 / 👎.

});
}

const body = yield* response.json.pipe(Effect.mapError(mapRequestError(operation)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify malformed 2xx bodies as API responses

When any Workers endpoint returns a successful status with empty or malformed JSON, response.json fails with an HTTP decode error, but mapRequestError converts every HttpClientError into WorkersApiNetworkError. This tells users to check their network and records externalNetwork even though the server response was reached; the same pattern affects get, upload-slot creation, and deploy responses, so decode failures should retain an API-response classification such as WorkersApiUnexpectedStatusError.

AGENTS.md reference: apps/cli/AGENTS.md:L370-L372

Useful? React with 👍 / 👎.

Comment on lines +398 to +400
const worker = yield* getWorker(api, projectRef, name).pipe(
Effect.retry({ schedule: options.retrySchedule ?? WORKER_POLL_READ_RETRY }),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry only transient polling failures

When a build poll receives a deterministic failure such as 401, 403, or another non-retryable 4xx response, this unconditional retry applies the full 30-second schedule before returning the actionable error. This is especially plausible when credentials expire during a long build, and it makes the command appear hung despite retries being unable to change the outcome; filter the retry schedule to transport errors and transient statuses such as 429 or 5xx.

Useful? React with 👍 / 👎.

: HttpClientRequest.put(slot.url)
).pipe(HttpClientRequest.bodyUint8Array(archive, "application/gzip"));

const response = yield* client.execute(request).pipe(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the presigned upload request

When the object-store endpoint accepts the connection but stalls while receiving the archive or returning its response, this direct HttpClient.execute has no deadline and can leave workers push pending indefinitely. Unlike the Management API client, which wraps requests in a 60-second timeout, this upload bypasses that retry/timeout policy; apply an explicit upload deadline, potentially bounded by the slot's expiry, so the command eventually returns WorkerUploadFailedError.

Useful? React with 👍 / 👎.

johnstonmatt added a commit that referenced this pull request Aug 21, 2026
… the upload URL

Addresses the review findings on #6262.

Two failures happened only after the remote project had already changed. `-o env`
cannot encode the payload's `workers` array, but that was discovered at emit
time — after every upload, deploy and build poll — so the command exited
non-zero having deployed, inviting a retry that deployed again. And an absent
optional `image_version` was left in the payload as `undefined`, which
smol-toml cannot represent, so `-o toml` threw at the same point. Both are now
settled before the first request: `legacyRejectUnsupportedWorkersOutput` runs up
front, and the field is spread conditionally like `url` beside it.

Packaging silently tolerated a filesystem it could not read. An unreadable file
was archived as zero bytes and an unreadable directory dropped its whole subtree,
so `push` reported success for an image built from an application with a hole in
it. Both propagate now. The redundant `Number()` around `File.Info.mode` is gone
too — it is a plain number, and wrapping it invited the reading that it was an
`Option`.

The presigned upload URL was reaching the `--debug` log. Its query string is a
write-capable credential for the archive a deploy is about to build from, so it
does not belong in terminal scrollback or a CI log. Fixed at the logging
boundary rather than by giving the upload its own HTTP client: redaction in
`legacyHttpClientLayer` keeps the client injectable for tests and covers every
presigned URL the CLI might ever log, not just this one.

Also: the build-poll read retries on a wall-clock budget instead of three
back-to-back attempts, which a two-second blip exhausted while the surrounding
poll still had minutes left; `WorkersApiUnexpectedStatusError` classifies from
the status it carries, so a 401 reads as "log in" rather than as a service
failure across every Workers endpoint; a source of nothing but empty directories
is refused before an upload slot is minted, instead of deploying an image with no
handler; the runtime guess is only reported once the source is known to exist;
and `config.toml` loading moved inside the finalizers, so a malformed config
still flushes telemetry — matching `config/push`, which already had it right.

Two findings are deliberately left for follow-ups: streaming the build context
rather than buffering it, and an ignore mechanism so `.env` and `.git` can be
kept out of the uploaded archive. Both are new work rather than defect fixes.
The `SIDE_EFFECTS.md` also now records the telemetry and linked-project writes,
the cache-miss API route, and why there is no live suite yet.
johnstonmatt added a commit that referenced this pull request Aug 21, 2026
…on a clock

Four follow-ups on #6262, three of them corners the previous round left open.

`fs.stat` was the last swallowed filesystem error in `collectEntries`. An entry
that disappears between `readDirectory` and the stat, or whose metadata cannot be
read, was dropped from the archive — the same "deploy an application with a hole
in it" outcome as the reads that were fixed, just losing a whole entry instead of
its contents.

`createTar` throws `TarPathTooLongError` for a name USTAR cannot represent, and
calling it directly inside the generator made that a defect rather than a typed
failure. `withJsonErrorHandling` only catches the failure channel, so
`--output-format json` would have terminated with no structured error payload for
an error the class explicitly declares as user-actionable. Wrapped in
`Effect.try`, narrowing to that class and letting anything else stay a defect —
because anything else there really is a bug.

The transient-failure integration test was waiting on the real clock. The outer
poll schedule was injected but the per-read retry was not, and that one is spaced
in seconds, so a 500-then-200 sequence sat for two seconds and more transient
responses would have pushed it toward thirty. Both schedules are parameters now;
that test went from ~2s to 22ms.

`--instances` is bounded at the parser, the way `[workers.<name>] instances` is
bounded in the config schema. `--instances=-1` was accepted by the integer flag
and carried into the deploy spec, so an impossible scaling request reached the
remote endpoint only after the build context had been packaged and uploaded.
`Flag.filter` refuses it during parsing, consistent with how `--runtime` is
validated.
@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch from 86d0f6b to f02a2c3 Compare August 21, 2026 15:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f02a2c3e46

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// them; everything that can fail on its own — loading `config.toml`,
// validating names, discovering workers — belongs inside, so a malformed
// config still flushes telemetry. Same shape as `config/push`.
const projectRef = yield* resolver.resolve(flags.projectRef);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Flush telemetry when project resolution fails

When resolver.resolve fails—for example for an unlinked noninteractive project, an invalid --project-ref, or a failed interactive project lookup—the handler exits here before reaching the Effect.ensuring(telemetryState.flush) below, so telemetry.json is not persisted on that invocation. Fresh evidence beyond the earlier config-loading issue is that project resolution remains outside every finalizer; wrap resolution in an outer telemetry finalizer while keeping the linked-project cache conditional on obtaining a ref.

AGENTS.md reference: apps/cli/AGENTS.md:L286-L290

Useful? React with 👍 / 👎.

Builds and deploys workers into the linked project, and brings the
Management API seam with it. Registered under `deploy` as an alias, for
anyone reaching for the `supabase functions` verb out of habit.

Given no names it deploys every worker in the project, matching
`supabase functions deploy`, whose conventions this command set otherwise
mirrors. "Every worker" is the union of the directories under
`supabase/workers/` and the `[workers.<name>]` entries, so one with a
`source` pointing elsewhere is not missed, and the order is sorted rather
than whatever the filesystem returned. Deploys run one at a time: each is a
server-side container build, so interleaving them would both compete for the
alpha's per-project capacity and shred the progress output; the first
failure stops the run.

The flow is mint an upload slot, PUT the `.tar.gz` build context straight at
the presigned URL, deploy, then poll until `build_state` leaves `building`.
The upload carries no Supabase credentials: the signature in the URL is the
authorization, and the bytes never pass through the management API. That
signature is also a write-capable credential for the archive a deploy is
about to build from, so `legacyHttpClientLayer` redacts presigned URLs at
the logging boundary — `--debug` scrollback and CI logs are not where it
belongs, and redacting there covers every presigned URL the CLI might log
rather than only this one.

Polling is a `Schedule`, and the read inside it retries on a wall-clock
budget so a blip of a second or two does not throw away a deploy that still
has minutes of build ahead of it.

Which spec is sent depends on the runtime: a `dockerfile` worker sends a
context and no `spec.runtime`, a catalog runtime sends both, and a bare
`sandbox` sends the runtime alone and skips packaging, so it has no URL. A
directory with no `[workers.<name>] runtime` has one guessed from marker
files once the source is known to exist, reported on stderr with a nudge to
pin it down.

Everything that can fail deterministically fails before the remote project
changes. `-o env` and a `-o toml` payload carrying an absent optional are
settled up front rather than at emit time, where the command would exit
non-zero having already deployed and invite a retry that deployed again;
`--instances` is bounded at the parser the way the config schema bounds
`[workers.<name>] instances`, instead of carrying an impossible scaling
request through a packaged upload; and a source of nothing but empty
directories is refused before an upload slot is minted, rather than
deployed as an image with no handler.

The build context is packaged in-process rather than by shelling out to
`tar`, whose BSD, GNU and absent-on-Windows variants each produce a
different archive from the same tree. `tar.ts` writes USTAR directly: files,
directories and symlinks, refusing a value too large for an octal header
field instead of letting it spill into the next one and read back as a
plausible but wrong size. Symlinks are stored as links rather than followed
— anything pnpm installs is symlink-dense, so following them would inline
every dependency and walk into a link pointing at an ancestor. Every
filesystem error propagates: an unreadable file archived as zero bytes, a
dropped subtree or an entry lost between `readDirectory` and its stat all
mean a successful `push` reporting an image built from an application with a
hole in it.

The Workers routes answer 404 both for a project outside the alpha's
allow-list and for a ref that names nothing this account can see, so the
classification reads `error.code`: `not_found` raises
`WorkerProjectNotFoundError` naming the ref, `supabase link` and
`supabase login`, and anything unrecognized keeps the enrolment answer,
since that is what the allow-list has historically returned and guessing the
other way sends someone to check a ref that is fine.

This is the first command in this shell to call a v2 Management API route;
every other one here is a Go-parity port and uses v1 only.

Two findings are deliberate follow-ups rather than defects: streaming the
build context instead of buffering it, and an ignore mechanism so `.env` and
`.git` can be kept out of the uploaded archive.
@johnstonmatt
johnstonmatt force-pushed the FUNC-753/workers-push branch from f02a2c3 to 905115b Compare August 21, 2026 16:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 905115bc4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// `-o` asks for a machine-readable stdout, so nothing human may be written
// to it — `output.success` logs to stdout in text mode.
if (yield* legacyEmitWorkersMachineOutput(payload)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave table and CSV output in text mode

When workers push is invoked with the globally accepted -o table or -o csv, legacyWorkersMachineOutputRequested suppresses the normal deployment details and this call falls through to legacyEmitWorkersMachineOutput, whose final branch encodes TOML. These values are db-query-only according to shared/legacy/global-flags.ts:12-19, so Workers should treat them like an unset output flag rather than reporting a successful remote deployment in an unrelated format.

AGENTS.md reference: apps/cli/AGENTS.md:L300-L300

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant