Troubleshooting
When something goes wrong, start with the exact message you see in the UI, CLI, or browser network tab. docsfy usually passes backend errors through directly, so messages like Frontend not built, Invalid branch name, Unauthorized, or Variant '...' is already being generated are usually the shortest path to the fix.
Quick Triage
- If
/api/*works but/or/logindoes not, this is usually a frontend build problem. - If a generation fails almost immediately, before cloning starts, this is usually a provider CLI problem.
- If the error mentions
Invalid branch name,Remote branch ... not found, ordoes not match expected, check the branch rules first. - If you see
already being generated,Abort first, orMultiple active variants found, you hit a generation lock or race. - If you see
401, repeated redirects to/login, or a missing project you expected to see, check authentication, roles, and project access. - If the dashboard loads but does not update live, check WebSocket connectivity. The browser app can fall back to polling; the CLI
--watchpath cannot. - The backend health endpoint should return:
{"status":"ok"}
Common Settings To Check
Server-side environment comes from .env:
ADMIN_KEY=
AI_PROVIDER=cursor
AI_MODEL=gpt-5.4-xhigh-fast
AI_CLI_TIMEOUT=60
LOG_LEVEL=INFO
DATA_DIR=/data
SECURE_COOKIES=true
# DEV_MODE=true
CLI access comes from ~/.config/docsfy/config.toml:
[default]
server = "dev"
[servers.dev]
url = "http://localhost:8000"
username = "admin"
password = "<your-dev-key>"
Note: Browser login and CLI login are different. The browser uses the
docsfy_sessioncookie. The CLI sends the configured password/API key as a Bearer token.Warning:
ADMIN_KEYis required and must be at least 16 characters long. If it is empty or too short, admin access will not work and the server will not start cleanly.
Missing Frontend Build
What it looks like
- Visiting
/,/login, or another SPA route returns a 404. - The response detail says
Frontend not built. Run: cd frontend && npm run build. - The HTML page loads, but
/assets/*files 404 and the dashboard appears unstyled or broken. - The API works, but the browser UI does not.
Why it happens
In production, docsfy serves the React app from frontend/dist. If frontend/dist/index.html is missing, the SPA cannot load at all.
index = _frontend_dist / "index.html"
if index.exists():
return FileResponse(str(index))
raise HTTPException(
status_code=404,
detail="Frontend not built. Run: cd frontend && npm run build",
)
The frontend build is defined in frontend/package.json:
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest run"
}
}
If you deploy with Docker, the image build is supposed to compile the frontend before the runtime image is created:
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
How to fix it
-
Build the frontend:
shell cd frontend npm ci npm run build -
Restart the server after the build finishes.
- If you are using Docker, rebuild the image so the new
frontend/distis copied into the runtime image. - If only
/assets/*is failing, treat that the same way: rebuild the frontend and redeploy the full build output, not justindex.html.
Tip: If
/api/statusworks but/does not, the backend is probably fine and only the frontend build is missing.
Development mode
For local development, this repo can run Vite instead of serving a static build. That is controlled by DEV_MODE=true. In that mode the entrypoint runs npm ci and starts the Vite dev server automatically.
Provider CLI Failures
What it looks like
- A generation switches to
erroralmost immediately. - The error appears before cloning or page generation starts.
- One provider works, but another fails on the same machine.
- You omitted provider/model and the server picked defaults you were not expecting.
What docsfy checks
Before it clones a repository, docsfy checks whether the selected provider CLI is available:
cli_flags = ["--trust"] if ai_provider == "cursor" else None
available, msg = await check_ai_cli_available(
ai_provider, ai_model, cli_flags=cli_flags
)
if not available:
await update_and_notify(
gen_key,
project_name,
ai_provider,
ai_model,
status="error",
owner=owner,
branch=branch,
error_message=msg,
)
return
Supported providers are fixed in the backend:
VALID_PROVIDERS = ("claude", "gemini", "cursor")
What to check
- Make sure you are using one of the supported provider names:
claude,gemini, orcursor. - If you omitted
ai_providerorai_model, remember that the server uses its defaults. In this repo those defaults arecursorandgpt-5.4-xhigh-fast. - Remember that
docsfylogin and provider login are different. YourdocsfyAPI key or browser session authenticates you todocsfy, but Claude, Gemini, and Cursor still need to be authenticated separately on the server. - If you are running outside the official container, make sure the provider CLI is installed and authenticated on
PATHfor the same user that runsdocsfy-server. - A model suggestion in the UI is historical, not a live entitlement check. If a remembered model now fails, verify that the provider CLI still has access to it.
- If the provider CLI is installed but slow to start, increase
AI_CLI_TIMEOUT.
The Docker image installs these CLIs at build time:
RUN /bin/bash -o pipefail -c "curl -fsSL https://claude.ai/install.sh | bash"
RUN /bin/bash -o pipefail -c "curl -fsSL https://cursor.com/install | bash"
RUN mkdir -p /home/appuser/.npm-global \
&& npm config set prefix '/home/appuser/.npm-global' \
&& npm install -g @google/gemini-cli
ENV PATH="/home/appuser/.local/bin:/home/appuser/.npm-global/bin:${PATH}"
In Docker, generation runs the CLIs as this runtime user:
# Switch back to non-root user for runtime
USER appuser
# Ensure CLIs are in PATH
ENV PATH="/home/appuser/.local/bin:/home/appuser/.npm-global/bin:${PATH}"
# Set HOME for OpenShift compatibility (random UID has no passwd entry)
ENV HOME="/home/appuser"
Warning: Provider failures happen before cloning. If a run fails instantly, check provider installation, provider authentication, and
AI_PROVIDER/AI_MODELfirst, not just the repo URL.Note: The
cursorprovider is handled slightly differently:docsfyautomatically adds--trustwhen it checks and calls the CLI.
Branch Validation And Branch Mismatch
What it looks like
- The request is rejected immediately with
Invalid branch name. - A remote run fails with a clone error such as
Remote branch ... not found. - A local path run fails because the checked-out branch does not match what you asked for.
What counts as a valid branch
docsfy defaults to main and validates branch names before generation starts:
@field_validator("branch")
@classmethod
def validate_branch(cls, v: str) -> str:
if "/" in v:
msg = (
f"Invalid branch name: '{v}'. Branch names cannot contain slashes "
"— use hyphens instead (e.g., release-1.x)."
)
raise ValueError(msg)
if not re.match(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$", v):
msg = f"Invalid branch name: '{v}'"
raise ValueError(msg)
if ".." in v:
msg = f"Invalid branch name: '{v}'"
raise ValueError(msg)
return v
That means:
- Valid:
main,dev,release-1.x,v2.0.1 - Invalid:
release/v2.0,.hidden,../etc/passwd
The slash rule exists because the branch is part of URLs like /docs/<project>/<branch>/<provider>/<model>/....
Remote repository branch problems
Remote repositories are cloned with git clone --depth 1, and docsfy passes the branch if you supplied one. If that branch does not exist upstream, you will get a clone failure such as:
Clone failed: fatal: Remote branch 'nonexistent' not found
Local repository branch problems
When you generate from repo_path, docsfy checks the current branch of that working tree. If it does not match the branch you requested, it fails with an error like:
Branch 'main' does not match expected 'v2.0'
How to fix it
- Use the exact branch name that exists in Git.
- Replace slashes with hyphens when you need a branch that can safely live inside a
docsfyURL. - If you are using
repo_path, either check out the requested branch first or change the branch value you send. - If you omit the branch entirely,
docsfyusesmain.
Actual CLI syntax for a branch-specific run looks like this:
docsfy generate https://github.com/myk-org/for-testing-only --branch dev --provider gemini --model gemini-2.5-flash --force
Generation Conflicts And Stuck Runs
What it looks like
Variant 'project/branch/provider/model' is already being generatedCannot delete 'project/provider/model' while generation is in progress. Abort first.Cannot delete 'project' while generation is in progress. Abort running variants first.Multiple active variants found; use the branch-specific abort endpoint.Abort still in progress ... Please retry shortly.- The page count reaches the planned total, but the variant still shows
generatingwhile the activity log moves throughvalidating,cross_linking, orrendering. - A run that was
generatingbecomeserrorafter a restart.
Why it happens
docsfy prevents two jobs from generating the same owner/project/branch/provider/model at the same time:
gen_key = f"{owner}/{project_name}/{branch}/{ai_provider}/{ai_model}"
async with _gen_lock:
if gen_key in _generating:
raise HTTPException(
status_code=409,
detail=f"Variant '{project_name}/{branch}/{ai_provider}/{ai_model}' is already being generated",
)
The same lock also protects delete and abort operations from racing against active generation.
How to fix it
- If you started the same variant twice, wait for the first run to finish or abort it.
- If you only know the project name but more than one variant is active, abort the exact variant instead of aborting by name.
- If abort says it is still in progress, wait a moment and retry.
- If abort says the job already finished, refresh status before trying again.
- If page writing has finished but the variant is still
generating, check the activity log orcurrent_stagebefore treating it as stuck.validating,cross_linking, andrenderingare legitimate post-generation stages. - If the server restarted during generation, start a new run.
docsfyintentionally marks orphanedgeneratingjobs aserrorwith the messageServer restarted during generation.
The dashboard activity log uses these stage names directly:
export const GENERATION_STAGES = [
'cloning',
'planning',
'incremental_planning',
'generating_pages',
'validating',
'cross_linking',
'rendering',
] as const
Useful CLI commands:
docsfy status for-testing-only
docsfy abort for-testing-only --branch main --provider gemini --model gemini-2.5-flash
Tip:
--forceclears cached generation artifacts, but it does not bypass the active-generation lock.
Access, Login, And Permission Problems
What the status codes usually mean
401 Unauthorized: you are not authenticated, your API key is wrong, or your browser session expired.403 Admin access required: you reached an admin-only endpoint.403 Write access required.: your account isviewer, so you can read but not generate, abort, or delete.404 Not found: the project may exist, but you do not own it and have not been granted access.docsfyintentionally hides inaccessible projects behind a 404 instead of exposing them with a 403.
Browser and CLI behave differently
- Browser requests to
/docs/*redirect to/loginwhen you are not signed in. - API and CLI requests return JSON
401responses instead. - The login page shows
Invalid username or passwordfor bad credentials, andUnable to connect to serverfor connection failures.
Admin login is always username admin with the admin password.
Session cookie problems
The browser session cookie is created like this:
response.set_cookie(
"docsfy_session",
session_token,
httponly=True,
samesite="strict",
secure=settings.secure_cookies,
max_age=SESSION_TTL_SECONDS,
)
Warning: If you run
docsfyon plainhttp://localhostwithSECURE_COOKIES=true, login can appear to work and then immediately bounce back to/loginbecause the browser will not send a secure cookie over plain HTTP.
For local HTTP development, set SECURE_COOKIES=false.
Role and access restrictions
viewerusers can view projects they own or were granted, but they cannot generate or delete.repo_pathgeneration is admin-only. Non-admin users getLocal repo path access requires admin privileges.- If you are using a local repository path, you can also see:
Repository path does not exist: '...'Not a git repository (no .git directory): '...'
Shared project access
Project sharing is owner-specific. If the wrong owner was used when access was granted, the user still will not see the project.
The CLI commands for access management are:
docsfy admin access list <project> --owner <owner>
docsfy admin access grant <project> --username <user> --owner <owner>
docsfy admin access revoke <project> --username <user> --owner <owner>
Admin cleanup is owner-specific too. If an admin delete call fails with Project owner is required for admin deletion. Use ?owner=username (or ?owner= for legacy projects), rerun the delete against the exact owner namespace.
If the same project name and variant exist under more than one owner, you can also see ambiguity errors such as:
Multiple owners found for this variant, please specify ownerMultiple owners found for this variant (...). Contact an admin to resolve the ambiguity.Multiple owners have variants with the same timestamp, please specify owner
In that case, use the correct owner explicitly or ask an admin to clean up the ambiguous access.
CLI auth checks
If the CLI is failing, start with:
docsfy config show
If the CLI reports a redirect such as Error: Server redirected to /login. Check the server URL., verify the url, username, and password in ~/.config/docsfy/config.toml.
WebSocket Connectivity And Live Updates
What it looks like
- The dashboard loads, but live status updates never appear.
- The CLI
--watchcommand printsWebSocket connection failed: .... - The CLI prints
WebSocket connection closed unexpectedly.orTimed out waiting for progress update.. - Browser dev tools show
/api/wsclosing with code1008or1001. - The dashboard still refreshes, but only in roughly 10-second jumps after the socket drops.
How it works
- The browser connects to
/api/ws. - The server accepts either a valid
docsfy_sessioncookie or?token=<api-key>. - On connect, the server sends an initial
syncpayload. - The server sends
pingevery 30 seconds, expectspongwithin 10 seconds, and closes after 2 missed pongs with code1001. - Unauthenticated WebSocket connections are closed with code
1008. - During a live generation, progress can continue past page writing into post-generation stages such as
validating,cross_linking, andrendering.
The frontend reconnect logic is:
private attemptReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.debug('[WS] Falling back to polling')
this.startPolling()
return
}
const delay = this.getBackoffDelay()
this.reconnectAttempts++
console.debug('[WS] Reconnecting, attempt', this.reconnectAttempts)
this.reconnectTimer = setTimeout(() => this.connect(true), delay)
}
In the browser, docsfy retries the WebSocket 3 times with backoff, then falls back to polling /api/projects every 10 seconds.
Tip: If the dashboard stops updating instantly but still refreshes within about 10 seconds, the polling fallback is probably working exactly as designed.
Local development and proxies
The Vite dev server is already configured to proxy WebSockets to the backend:
'/api': {
target: API_TARGET,
changeOrigin: true,
ws: true,
}
If you are running behind another proxy or load balancer, make sure it preserves WebSocket upgrade requests for /api/ws.
CLI --watch specifics
The CLI derives its WebSocket URL from your configured server URL. That means a bad url in ~/.config/docsfy/config.toml can break --watch even if the generation itself still starts.
A real --watch example from this repo's test plan is:
docsfy generate https://github.com/myk-org/for-testing-only --branch dev --provider gemini --model gemini-2.5-flash --force --watch
If --watch fails, rerun without it and use docsfy status <project> while you fix the network or proxy issue. The browser app has polling fallback; the CLI watch path does not.
What to check
- If the close code is
1008, log in again or use a valid API key/token. - If the close code is
1001, the client is missing heartbeat replies or an intermediary is breaking the socket. Custom clients must answer serverpingmessages with a JSONpongreply. The built-in browser app and CLI already do this automatically. - If the site is served over HTTPS, the browser will use
wss://automatically. - If you are using Vite dev mode, make sure the dev server is running and port
5173is exposed. - If you are using a reverse proxy, confirm it supports WebSocket upgrades for
/api/wsand does not terminate idle connections too aggressively. - If the dashboard works but updates are delayed, wait for the 10-second polling interval before assuming it is stuck.
If none of the sections above fits, capture the exact error text, the provider, model, branch, whether you were using the browser or CLI, and whether /health returned {"status":"ok"}. Those details usually identify the failing layer very quickly.