Guide

Fix common publish errors

Most publish errors come from a small set of patterns — third-party auth libraries, localStorage persistence, bad SDK imports, missing lockfiles. Each has a specific fix. When in doubt, read the error — Ellivate's errors point at exact file+line.

"E404 Not Found — ellivate-client" during npm ci

Cause: Your code imports the SDK with a bare specifier — import { ellivate } from "ellivate-client". npm tries to resolve that as a registry package, fails with a 404, and the container build aborts.

Fix: Use a relative path:

import { ellivate } from "../ellivate-client";

The exact .. depth depends on how deep your file is. The Ellivate MCP's ellivate_import_path tool returns the correct path given your file's location; if you're using Claude Code / Cursor / Windsurf with the MCP installed, your LLM will get this right automatically.

"Your app uses localStorage for user data"

Cause: The storage translator found a localStorage.setItem or similar call it couldn't safely rewrite — usually because the value being stored isn't JSON-serializable, or the call is inside a dynamic expression the rewriter can't parse.

Fix: Swap to the KV SDK directly at that callsite:

// Before
localStorage.setItem("theme", JSON.stringify(currentTheme));

// After
await ellivate.kv.set("personal:theme", currentTheme);

The personal: prefix means each viewer has their own theme. Use shared: for app-wide settings.

"Third-party auth library detected"

Cause: The classifier found NextAuth, Clerk, Firebase Auth, Flask-Login, hand-rolled JWT cookie logic, or similar. Ellivate's shell provides identity — these libraries double-up the sign-in flow and break the UX.

Fix: Remove the auth library. Replace identity reads with:

// JS / TS
const viewer = await ellivate.viewer();
// { id, email, username, verified } or null

// Python
viewer = ellivate.viewer()

Then flip Requires auth on in the app dashboard — unauthenticated visitors will be sent through the shell's sign-in before reaching your app.

"Missing lockfile"

Cause: Node apps need package-lock.json. Python apps need a pinned requirements.txt (not just loose version ranges). Without them, the container build can install different versions each time and produce non-deterministic behavior.

Fix (Node):

npm install

commits package-lock.json, then republish.

Fix (Python):

pip freeze > requirements.txt

captures the exact versions you're running locally.

"App isn't listening on 0.0.0.0"

Cause: Flask's default app.run() binds to localhost, which isn't reachable inside the Railway container. Same for FastAPI if you run uvicorn main:app without a host flag.

Fix: Ellivate normally rewrites this at publish time. If the error fires, it's because the binding is in an unusual shape the rewriter missed. Add explicit binding in your launch block:

# Flask
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8000)))

Or, ideally, let Ellivate's Procfile handle it — just define app at module scope and don't call .run().

"Prerender failed at build time" (Next.js)

Cause: A server component called the Ellivate SDK at build time, when env vars aren't set yet. Next's App Router tries to statically prerender any page that doesn't opt out.

Fix: Ellivate's SDK sends cache: "no-store" on all fetches — that already signals Next to render dynamically. If you still see this error, check:

  • Your code isn't wrapping the SDK call in something that cached the response (e.g. a custom fetch wrapper that sets cache: "force-cache").
  • You don't have an explicit export const dynamic = "force-static" on the page that uses the SDK.

"Iframe loads but data is empty"

Cause: The handoff cookie isn't reaching your server-rendered code. A common culprit is an auth middleware that redirects /ellivate-handoff before the token lands.

Fix: Ellivate patches middleware.ts at publish time to early-return for handoff requests. If you added the middleware after publishing, republish to re-run the patch. Or manually guard it:

// middleware.ts
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname === "/ellivate-handoff") {
    return NextResponse.next();
  }
  // ... your other logic
}

"Your data model needs a relational database"

Cause: The classifier detected sqlalchemy, complex joins, or a data pattern that's not JSON-serializable. Ellivate's KV can't model those cleanly.

Fix: Either simplify the model (often possible — "users with posts" can be shared:users and shared:posts:<userId>), or upgrade to a plan with an external database. Ellivate will point you at the right tier in the gate form.

"Playwright hardcodes Chrome channel"

Cause: Your Playwright code does p.chromium.launch(channel="chrome"). Ellivate runs a headless Chromium inside the container; the specific Chrome channel isn't available.

Fix: Ellivate normally rewrites this automatically. If the error fires, remove the channel argument:

browser = await p.chromium.launch()

"Your app needs X — handing this off to your IDE"

When the publish pipeline can't fix something on its own (missing dependency, a query shape we don't translate, an import path you renamed but didn't update everywhere), it emits a local-fix handoff — a structured payload that names exactly what's wrong and which command to run. You'll see it as the final message on ellivate publish:

✗ FAILED
  rootCause: LOCKFILE_OUT_OF_SYNC (blocker)
  Your app needs TypeScript type packages installed.
  Suggested fix:
    [safe] npm install --save-dev @types/react @types/node

If you have Claude Code / Cursor / Windsurf running in this project: the Ellivate skill picks up the handoff automatically. Ask Claude "apply the ellivate handoff" or just paste the message — it will run the suggested commands, apply any required edits, and tell you to re-run ellivate publish.

If you're publishing from the dashboard with no IDE attached: read the diagnosis, run the suggested commands in a terminal yourself (or fix the file the message names), then re-upload. The handoff payload is deliberately verbose because it's designed for both humans and AI to consume.

The handoff replaces the old "Ellivate tried 3 times and gave up" behavior. One clear diagnosis + one actionable fix beats three opaque retries.

When the error is a Class 1 bug

Class 1 bugs are Ellivate's fault — the classifier picked the wrong framework, the rewriter leaked prose, the injector put an import in the wrong place. If the error message looks internal (stack trace in Ellivate's code, or the failure is clearly on the pipeline side rather than your code):

  • Check the dashboard issues tab. Most Class 1 bugs surface as an explicit issue with a reproducer.
  • Contact support (ellivate.ai/support) with the app ID — we can inspect the publish history and patch the pipeline.

What's next