Run the lightweight self-test first:
studyloop self-test
studyloop self-test --jsonThen run the deeper environment checks:
studyloop doctor
studyloop doctor --jsonstudyloop self-test is safe immediately after installation. It does not run
doctor --fix, start services, contact providers, or write agent files.
When a local checkout behaves differently from CI, resync the lean development profile and rerun the core gates:
just sync-dev
just lint
just typecheck
just testUse just sync-full only when validating optional extras. The full profile
pulls heavier optional stacks such as semantic search and TTS dependencies.
Use profile checks when a change touches a specific optional surface:
just test-web
just test-content
just test-semanticIf test-semantic skips because numpy or embedding dependencies are missing,
run:
just sync-semantic
just test-semanticFor local-only use:
studyloop webFor LAN use:
studyloop web --lanConfigured LAN passwords are not printed. Generated one-time passwords are printed once. If a phone or tablet cannot connect, check that the shown LAN URL uses the host's real LAN address and that the device is on the same network.
The live console has two renderers, chosen by the session's transport:
transport |
Renders as |
|---|---|
pty (default) |
xterm.js, fed by the PTY over a WebSocket |
acp |
structured ACP chat events |
Anything else has no browser renderer. The console reports an explicit
unavailable state — status No terminal available, plus a message naming the
transport it cannot render and telling you to end the session and start it again
on the browser terminal or ACP. In practice this should not happen any more:
ttyd (the only other transport that ever existed) is fully retired, both the
browser surface and the server transport
(ADR-0005,
ADR-0008). STUDYLOOP_TRANSPORT=ttyd and
{"transport": "ttyd"} are both rejected outright (422/error) rather than
accepted and rendered as unavailable — if you see this state, it means the
server returned a transport value this console genuinely doesn't recognise,
which is worth reporting as a bug rather than working around.
If the environment sets STUDYLOOP_TRANSPORT to anything other than pty,
unset it and start a new session:
env | grep STUDYLOOP_TRANSPORT
unset STUDYLOOP_TRANSPORT
studyloop webInstalling ttyd does nothing — no code path spawns, proxies, or accepts
it any more. The retired design records remain in the repository for
maintainers, including the reasoning for retiring first the browser surface
and then the server transport.
If the transport is already pty and the panel still reports no terminal, the
server did not return a connection for the session. Ending and restarting the
session clears this; the message says so rather than leaving a blank pane.
The panel should reattach on its own. What holds today:
- The session survives the refresh. A disconnect starts a detach grace
window on the server (90 s by default;
STUDYLOOP_WS_GRACE_SECONDSoverrides it), so the agent process keeps running andGET /api/session/statestill reports the session after the reload. - The reattach is automatic.
liveAgentConsole.init()readsGET /api/session/stateon load and re-adopts a live session it owns, restoring the terminal and reporting Reattached rather than Starting.
An empty terminal after a refresh is therefore a real fault worth investigating, not expected behaviour. The most likely cause is a reload that took longer than the grace window, which genuinely ends the session.
If a refresh leaves an empty terminal:
-
Confirm a session is actually live:
GET /api/session/stateshould report it. If the grace window has already expired, the state response says why the last session went away. -
Check the browser console for a JS error during
init()— a thrown initialiser stops the adopt step from even being attempted. -
Confirm the static assets being served are current.
studyloop webserves the installed package's assets, so a stale editable install serves stale JS:uv run studyloop install tools --skip-sync
To get back to work, end the session from the UI and start a new one — the
pane says what happened rather than staying blank. Sessions started from the
CLI in tmux (not through the web UI) have their own recovery path:
studyloop study --resume reattaches to the running agent, or rebuilds the
conversation from history if the tmux session is gone.
Use the web Settings panel or environment variables for provider keys. Raw provider keys must never appear in logs, screenshots, or issue reports.
For provider checks:
studyloop self-test
studyloop doctor --category depsstudyloop self-test only verifies that the web module imports. It does not
call OpenAI, OpenRouter, Gemini, Anthropic, Bedrock, Ollama, or other providers.
The Generate panel runs one content-generation job at a time. If another job is
active, POST /api/content/generate returns 409 and the browser shows a
visible busy/conflict banner.
Use this order:
- Check the Generate panel status and progress rows.
- Wait for the active job to finish if the panel says another job is running.
- If the local process was killed mid-job and the UI never clears, restart
studyloop web. - Rerun generation from the same course/scope. The default Merge policy de-duplicates existing cards/questions and is the least destructive retry.
The Cards / questions per source field is sent as count_per_source. It is
copied into each GenerationTask.count and included in the provider prompt for
every selected source and kind.
This is a target, not a filesystem quota. Live providers can under- or over-produce if they do not follow the prompt; invalid shapes fail validation and appear as task errors. StudyLoop does not substitute generated-looking placeholder cards when a provider fails. Use the Generate panel plan/progress line to confirm the requested count, provider, and model before comparing output files.
The Course Explorer provider/course tree is cached by a visible tree
fingerprint, not only the top-level directory timestamp. Adding or deleting
nested source courses should refresh on the next tree request, while generated
output folders such as flashcards/ and quizzes/ intentionally do not
invalidate the visible tree.
If the panel looks stale:
- Refresh the browser.
- Confirm the source file lives under
content.base_path. - Confirm the source file has an allowed suffix:
.md,.markdown, or.txt. - Restart
studyloop webif the process has been running through manual file moves or external sync conflicts.
Do not delete explorer_fts.db for a stale provider/course list. That file is
only the derived search index.
Course Explorer search uses the derived SQLite FTS cache at
<session_db_dir>/explorer_fts.db. The cache is built lazily on first search and
refreshed from source lesson metadata on subsequent searches.
If search results are stale but the course tree is correct:
- Search again after saving the source file.
- Restart
studyloop webif the process was interrupted during indexing. - Delete
explorer_fts.dbonly for search-index issues; it will be rebuilt on the next search and is not part ofsessions.dbmigrations.
Struggles and progress live in sessions.db and are surfaced through
study_progress. Course Explorer writes web-marked struggles through
POST /api/history/struggling-topics, including provenance columns such as
source_course and source_section.
Run:
studyloop self-test
studyloop doctor --category databaseIf the database check fails, fix that first. If it passes but the Generate
panel's Topic I'm struggling on list is empty, confirm that the relevant
struggle is inside the selected window and has confidence='struggling'.