Troubleshooting
Symptom-first fixes for auth, CRUD, storage, AI, realtime, and deployment issues
Use this page when something looks broken and you need the shortest safe path from symptom to fix. Each row keeps the public contract small: no provider keys, session tokens, storage grant URLs, database URLs, or internal platform paths should appear in app code, logs, screenshots, or support posts.
Fast Checks
| Symptom | Likely cause | Fix | Verify |
|---|---|---|---|
Frontend calls http://localhost:5456 after deploy |
The auth or API client was created without the deployed base URL | Define one baseUrl from import.meta.env.VITE_API_URL and pass it to createAuthClient(baseUrl) and createGencowClient({ api, baseUrl, auth }) |
Browser network requests go to your *.gencow.app or custom domain backend |
Expected object from a CRUD list query |
Current explicit docs contract is to pass an args object to createCrud().list |
Use useQuery(api.tasks.list, {}) for the default list, or pass { page, limit } / { filters } |
Response is { data, total, nextCursor? } |
result.map is not a function |
createCrud().list returns an object, not an array |
Render result.data.map(...) and use result.total for counts |
Empty lists render an empty state instead of crashing |
| Private file URL returns 403 | Hosted storage is private by default | Return a fresh ctx.storage.createReadGrant(storageId, { ttlSeconds }) URL for browser previews, or store intentionally public assets with { visibility: "public" } |
Private bare URL is 403, read grant URL is 200, public asset URL is 200 |
| Login works locally but deployed frontend is logged out | Auth lifecycle and query/mutation calls use different origins | Use the same baseUrl for createAuthClient(baseUrl) and createGencowClient({ api, baseUrl, auth }) |
Sign in, refresh the page, then run an auth-required query |
| Realtime does not refresh a private list after a custom mutation | Custom mutations do not know which query to refresh | Call ctx.realtime.invalidate("tasks.list") after the write |
The subscribed useQuery(api.tasks.list, {}) refetches over HTTP with the user's JWT |
| AI request returns 402 or upgrade-required | The selected model or service usage requires a higher plan or available service credits | Pick an allowed model for the plan, reduce usage, or upgrade the plan | Error code is user-actionable and appears before provider call side effects |
| AI request returns 429 | Provider admission or service credit pacing is limiting the request | Retry later with backoff, lower request size, or check plan/provider status | Public logs show safe codes such as provider_tpm_limited, not raw prompts or provider keys |
| AI request returns 403 | Auth, plan entitlement, storage grant, or provider credential state may be blocking the request | First check app auth, plan/model access, and whether any private file was passed without a read grant | The response body should be public-safe and should not include secrets |
AI app redeploy fails with missing ai or @ai-sdk/openai |
Platform-provided dependency resolution drift | Redeploy with the latest CLI/runtime after the platform fix is released; do not add provider keys to app code as a workaround | Startup logs no longer show module resolution errors |
| Production static build points at localhost | VITE_API_URL was missing at frontend build time |
Build with VITE_API_URL=https://<app>.gencow.app npm run build, then deploy static files |
Generated JS contains the deployed origin, not localhost |
| Custom domain SSL or routing fails | DNS, apex/www policy, or active binding mismatch | Check gencow domain status, use www CNAME for the simple path, and keep apex/www policy explicit |
https://www.example.com serves the intended app or redirects only by configured policy |
Auth and API Base URL
Create one shared base URL and reuse it for auth lifecycle, queries, mutations, and realtime tickets.
// src/lib/gencow.ts
import { createAuthClient, createGencowClient } from "@gencow/client";
import { api } from "../gencow/api";
export const baseUrl = import.meta.env.VITE_API_URL ?? "http://localhost:5456";
export const auth = createAuthClient(baseUrl);
export const apiClient = createGencowClient({ api, baseUrl, auth });// src/main.tsx
import { GencowProvider } from "@gencow/react";
import { apiClient } from "./lib/gencow";
root.render(
<GencowProvider apiClient={apiClient}>
<App />
</GencowProvider>
);Do not rely on createAuthClient() to auto-read VITE_API_URL. Passing the URL explicitly is the stable contract.
CRUD Lists
createCrud().list returns an object with the rows and metadata. Treat it like a paginated result even when you only need the first page.
import { useQuery } from "@gencow/react";
import { api } from "../gencow/api";
export function TaskList() {
const { data: result, isLoading, error } = useQuery(api.tasks.list, {});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Could not load tasks.</p>;
if (!result || result.data.length === 0) return <p>No tasks yet.</p>;
return (
<ul>
{result.data.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}For paging, pass page and limit and disable navigation when there is nowhere to go.
const limit = 20;
const { data: result } = useQuery(api.tasks.list, { page, limit });
const totalPages = Math.max(1, Math.ceil((result?.total ?? 0) / limit));Storage 403s
Hosted storage is private by default. A private object should not be shown to the browser through a long-lived bare URL.
export const listFiles = procedure.query
.name("files.list")
.handler(async ({ context: ctx }) => {
const rows = await ctx.db.select().from(files);
return Promise.all(
rows.map(async (row) => ({
id: row.id,
name: row.name,
previewUrl: (await ctx.storage.createReadGrant(row.storageId, {
ttlSeconds: 300,
disposition: "inline",
})).url,
}))
);
});Use ctx.storage.getPublicUrl(storageId) only for assets that were stored with { visibility: "public" }.
AI Errors
| Code or status | Meaning | User action |
|---|---|---|
model_requires_upgrade / 402 |
The model is not available on the current plan | Choose a lower-tier model or upgrade |
provider_tpm_limited / provider_rpm_limited / 429 |
Provider admission is pacing the request | Retry later, reduce request size, or upgrade capacity |
| 403 | Auth, plan entitlement, storage grant, or provider credential state blocked the request | Check app auth, selected model, and whether private files use read grants |
server_error |
Unexpected platform or provider failure | Retry once, then check status/logs without exposing prompts or secrets |
For cloud apps, use generated gencow/ai.ts or createGencowAI() helpers. Do not put provider SDK keys or raw provider clients in app code to work around errors.
Deploy and Environment
Before deploying a frontend build, confirm the generated app talks to the intended backend:
VITE_API_URL=https://my-app.gencow.app npm run build
gencow deploy --static dist/If auth works in development but not after static deploy, rebuild the frontend with the deployed VITE_API_URL and redeploy the static assets.