Skip to content
Open
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
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1675,7 +1675,7 @@ was refused, while a **2.32 MB** one was accepted. That brackets the server's
real ceiling to somewhere in **(2.32 MB, 8.20 MB]** — and no further, because
each probe costs a real submission.

🔴 **The CLI now refuses above 10485760 bytes of body — and that number is not from
🔴 **The CLI now refuses at or above 10485760 bytes of body — and that number is not from
inside the bracket.** It is Next.js's `proxyClientMaxBodySize` default, which applies
because civitai's `src/proxy.ts` matches `/api/v1/:path*` and its `next.config.mjs` sets no
override. It is external to this CLI and to civitai.
Expand All @@ -1693,6 +1693,20 @@ sanity check, not evidence; the provenance is what the number rests on:
The refusal costs no upload: it is checked against the marshalled document itself, before
the request is built.

**Why "at or above" and not "above".** A body of *exactly* 10485760 is reachable in practice —
the JSON envelope for a `--allow-dirty` submit is 96 bytes, base64 output is always a multiple
of 4, so a 7,864,246-byte zip lands on the ceiling to the byte. Which side of it the platform
sits on is genuinely unresolved: Next.js's source reads `bytesRead > bodySizeLimit`, which would
accept that body, while an end-to-end measurement of the real endpoint answered `413` at exactly
10485760 (and `401` — i.e. the body got through to auth — one byte below). One of those is wrong
and this CLI cannot settle it without spending a real submission.

It does not need to. The two mistakes are not equally priced: refusing one byte early costs an
author a flag they are already told about, and accepting one byte too many costs the whole
upload and returns an error naming nothing about size. So the guard takes the cheap side, and
this paragraph records that it is a choice under uncertainty rather than a fact about the
server.

⚠ **It is still a vendored number, and `--allow-oversize` is the way out.** Nothing in this
CLI notices the day civitai raises that default — and
[civitai#4793](https://github.com/civitai/civitai/issues/4793) names raising it as the change
Expand Down Expand Up @@ -3971,7 +3985,7 @@ credited it to the wrong command.)
| `(token scope not reported by the server — Buzz capabilities unknown)` | `civitai whoami` got no `tokenScope`, so **Buzz** read/spend are unknowable and are omitted rather than printed as `no`. Scoped to Buzz deliberately: the **Submit Apps** row is still shown above it, because a personal key's submit answer does not depend on the scope mask. | [Submit & auth](#submit--auth) |
| `not permitted to read this app's analytics (403)` | `app metrics` needs the **Apps submit scope**. An OAuth `civitai login` carries it — unless the token was minted before the scope existed, in which case re-run `civitai login`. A full-scope personal API key also works. | [App metrics](#app-metrics) |
| `block lacks ai:write:budgeted scope` | Printed by your app at runtime under `dev:live`. The dev token was minted **without** `--spend`, so the CLI filtered the budgeted-spend scope out — it never requests that scope implicitly, even when your manifest declares it. | [Local dev loop](#local-dev-loop-harness-mock-vs-live) |
| `the server can receive` | The submit body exceeds **10485760 bytes** and `app submit` refused **before uploading**, so it cost you nothing. The number is the base64 JSON body, ~4/3 of the zip — shrink the bundle (the message lists the largest entries). ⚠ That ceiling is a *vendored* framework default, not something the server told us: if you believe it has been raised, `--allow-oversize` submits anyway. | [Submit & auth](#submit--auth) — *How big can a bundle be?* |
| `the server can receive` | The submit body reached or exceeded **10485760 bytes** and `app submit` refused **before uploading**, so it cost you nothing. The number is the base64 JSON body, ~4/3 of the zip — shrink the bundle (the message lists the largest entries). ⚠ That ceiling is a *vendored* framework default, not something the server told us: if you believe it has been raised, `--allow-oversize` submits anyway. | [Submit & auth](#submit--auth) — *How big can a bundle be?* |
| `insufficient Buzz` / `generation disabled` | Not credential problems, which is why they exit `1` rather than `3` — a script must not loop on `civitai login` for either. | [Exit codes specific to `generate`](#exit-codes-specific-to-generate) |
| `rate limited (429)` | 🔴 **This one message has TWO exit codes — branch on the code, never on the text.** `2` when it is really the deep-paging cap (the message says "too many pages" / "use cursors instead"): the request is structurally doomed, so use `--cursor` rather than `--page`. `6` for a genuine throttle; retry with backoff. **Not `5`** — a 429 *can* exit `5`, but when it does it prints the next row's message instead of this one. | [Exit codes](#exit-codes) |
| `Civitai returned HTTP` | **A retriable status that survived every read retry** — printed for the status that persisted, whichever it was: `502`/`503`/`504`, or a `429` that carried `Retry-After`. Exits **`5`** in every case, because once the retries are exhausted this is a service-availability failure and `5` is the code to retry on. Read the number in the message to know which you hit. ⚠ For the `429` case only: the header is checked *before* the message, so a deep-paging-cap `429` that carried `Retry-After` lands here too — exit `5` — rather than on `2`, and you will **not** see `rate limited (429)`. | [Exit codes](#exit-codes) |
Expand Down Expand Up @@ -4007,7 +4021,7 @@ credited it to the wrong command.)
| `nothing index.html loads reaches it` | The **strong** tier: the emitter is in your project but nothing the browser loads reaches it — an orphan file. Copying `civitai-host.js` in is only half the fix; it has to be referenced too. | [The host handshake](#the-host-handshake-block_ready) |
| `no lockfile is committed` / `is not a lockfile` | The platform build installs **strictly** from the committed lockfile, so a missing one — or a zero-byte one created with `touch` — fails the build server-side. A lockfile is generated by the package manager, never hand-written. | [Validate fidelity](#validate-fidelity) |
| `refusing to submit without --yes` | A submit that would really upload asked for confirmation and found no TTY. Pass `--yes` in CI, or `--package-only` to just write the .zip. | [Command reference](#command-reference) |
| `What this CLI sent` / `largest entries in the bundle` | Not an error of its own — it is printed **under** a failed submit, and it is the CLI's account of what left your machine: the exact bytes that went on the wire, and the biggest entries they were made of. It appears because the server's own message may name nothing you can act on; `400: Invalid JSON` is the measured case ([#423](https://github.com/civitai/cli/issues/423)), an error about the *parse* raised downstream of an oversized request body. **The CLI does not claim to know why the submit failed** — it cannot see the server's limits. ⚠ Since `#585` it *does* refuse a body over **10485760 bytes** up front, so reaching this block at all means the body was under that ceiling (or you passed `--allow-oversize`); `pkgzip`'s own caps remain much higher and still are not a server mirror. If the bundle is large, drop what the platform build does not need and retry. Not printed for a `401`/`403`/`429`. | [Submit & auth](#submit--auth) — *How big can a bundle be?* |
| `What this CLI sent` / `largest entries in the bundle` | Not an error of its own — it is printed **under** a failed submit, and it is the CLI's account of what left your machine: the exact bytes that went on the wire, and the biggest entries they were made of. It appears because the server's own message may name nothing you can act on; `400: Invalid JSON` is the measured case ([#423](https://github.com/civitai/cli/issues/423)), an error about the *parse* raised downstream of an oversized request body. **The CLI does not claim to know why the submit failed** — it cannot see the server's limits. ⚠ Since `#585` it *does* refuse a body of **10485760 bytes** or more up front, so reaching this block at all means the body was under that ceiling (or you passed `--allow-oversize`); `pkgzip`'s own caps remain much higher and still are not a server mirror. If the bundle is large, drop what the platform build does not need and retry. Not printed for a `401`/`403`/`429`. | [Submit & auth](#submit--auth) — *How big can a bundle be?* |
| `Your repo may be behind what was last released` / `Resubmitting the version that is already live is almost always an accident` / `That version is approved but not live` | The **monotonic-version guard**: `civitai app submit` refused because the manifest version is not strictly above the highest **approved** version of that app, and approving an older (or identical) version supersedes the newer one — replacing the live deployment when that version is serving. Exit `1`; `--allow-downgrade` submits anyway. **The second line tells you which of four cases you are in, and each says only what is actually known.** A **lower** version against a version that is **live** replaces that deployment on approval, so your repo may be behind what was last released — bump the manifest, or `civitai app pull` first. A **lower** version against an approved row that is **not** live deploys code older than the highest approved version; no deployment of that version is being replaced, because none is running. The **same** version against a deployment that is really live is almost always an accident. The **same** version against an approved row that is **not** live is a resubmit of a deploy that has not landed — still building, still deploying, or failed — which is a plausible deliberate act, and `--allow-downgrade` submits that version again. **"Live" is the server's own answer** (the `liveUrl` it returns, the same field `civitai app status` prints `Live at:` from), not a guess from the deploy state — so a legacy approval that predates deploy-state tracking is correctly treated as serving. | [Exit code 1](#exit-code-1) |
| `from a dirty git work tree` / `that go into the bundle are not committed` | The **dirty-work-tree guard**: `civitai app submit` refused because files that go into the bundle are uncommitted. The bundle is packaged from what is on disk, so approving one deploys code that exists in no commit and nothing afterwards can say which revision is live. The refusal names the paths (`git status` spelling, relative to the packaged directory) — commit them, or pass `--allow-dirty` to submit the tree exactly as it is. Exit `1`. **It only fires inside a git repository**: a scaffolded app that was never `git init`ed submits unchanged, and so does a machine with no `git` on `PATH`. Paths the packager never ships — `dist/`, `node_modules/`, a stray `.zip`, a `.env.local`, anything `.gitignore`d, and any symlink (the packager bundles regular files only) — are not counted, because they are not in the bundle. 🔴 **A repository with no commits yet refuses *everything*, `block.manifest.json` included** — a `git init` you have not committed into means nothing in the bundle is in a commit, which is exactly what the guard checks. That is the row between "no repo" and "repo, dirty": make the first commit, or pass `--allow-dirty`. Scaffolding never puts you here — `civitai app create` / `app init` run no `git init`. **A `git mv` counts as two changes**, because the bundle gains the destination and loses the original; both are named, even when the destination is a path the packager drops (`git mv src/App.tsx dist/App.tsx` is refused, naming `src/App.tsx`). | [Exit code 1](#exit-code-1) |
| `look like they hold credentials` | A **warning**, not a refusal — the submit continued and the exit code is unchanged. A file the packager KEPT holds a line shaped like a credential, and the bundle goes to the platform and to a human reviewer, where it cannot be recalled. The warning prints `path:line` and the key name and deliberately **not** the value — open the file to see what matched. It fires on every path including `--package-only`, is scoped to what was really packaged, and is a heuristic in both directions: a false positive is possible, and silence is not a guarantee. ⚠️ On a real `submit` it prints *after* the confirmation, so it reports the leak rather than preventing it — `--package-only` is the path where you can still act. If it says the scan **stopped** at its byte budget, the files after that point were not checked at all. | [Submit & auth](#submit--auth) — *What looks like a credential* |
Expand Down
Loading
Loading