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.