odysseus-dev/odysseus · error · HTTPException
Assistant session could not be resolved
Error message
Assistant session could not be resolved
What it means
Raised by GET /api/assistant/session when the per-user assistant CrewMember cannot be resolved, or is resolved but its pinned session_id is NULL/empty. The route first lazily seeds the assistant via task_scheduler.ensure_assistant_defaults(owner); the 500 means seeding produced no CrewMember row, or produced one whose Session was never created or linked. It is a server-state defect, not a client payload problem.
Source
Thrown at routes/assistant_routes.py:128
# user — safe to call again, it's idempotent.
await task_scheduler.ensure_assistant_defaults(owner)
db = SessionLocal()
try:
crew = db.query(CrewMember).filter(
CrewMember.owner == owner,
CrewMember.is_default_assistant == True, # noqa: E712
).first()
return crew
finally:
db.close()
@router.get("/session")
async def get_assistant_session(request: Request):
"""Resolve (or lazily create) the pinned Assistant session for this user."""
owner = _owner(request)
crew = await _get_or_create(owner)
if not crew or not crew.session_id:
raise HTTPException(status_code=500, detail="Assistant session could not be resolved")
return {
"session_id": crew.session_id,
"crew_member_id": crew.id,
"name": crew.name,
}
@router.get("/settings")
async def get_assistant_settings(request: Request):
"""Return CrewMember fields + the three check-in task rows + task IDs for logs."""
owner = _owner(request)
crew = await _get_or_create(owner)
if not crew:
raise HTTPException(status_code=500, detail="Assistant not available")
db = SessionLocal()
try:
tasks = db.query(ScheduledTask).filter(
ScheduledTask.owner == owner,
ScheduledTask.crew_member_id == crew.id,View on GitHub (pinned to f9235ebbf1)
Solutions
- Re-hit GET /api/assistant/session (or restart the app so the startup seeding hook runs for each user) — seeding is idempotent and will link a session on the next pass.
- Inspect the CrewMember table: SELECT id, session_id, is_default_assistant FROM crew_members WHERE owner='<user>'; if session_id is NULL, delete the broken row so the next request reseeds it cleanly.
- Check application logs for exceptions inside task_scheduler.ensure_assistant_defaults (DB locked, constraint violation, missing Session table).
- If it persists after a clean reseed, verify the database schema is current (run the app's migration/init path) — a missing sessions table makes the seed silently skip session linking.
Example fix
// before (client treats it as fatal):
const r = await fetch('/api/assistant/session');
if (!r.ok) throw new Error('assistant broken');
// after: retry once — lazy seeding is idempotent and usually succeeds on the 2nd pass:
let r = await fetch('/api/assistant/session');
if (r.status === 500) {
await new Promise(res => setTimeout(res, 1000));
r = await fetch('/api/assistant/session');
}
if (!r.ok) throw new Error('Assistant session could not be resolved'); Defensive patterns
Strategy: retry
Validate before calling
async function getAssistantSession(retries = 1) {
for (let i = 0; ; i++) {
const r = await fetch('/api/assistant/session', {credentials: 'include'});
if (r.ok) return r.json();
if (r.status === 500 && i < retries) {
await new Promise(res => setTimeout(res, 1000)); // lazy seed is idempotent
continue;
}
throw new Error(`Assistant session could not be resolved (HTTP ${r.status})`);
}
} Type guard
function isAssistantSession(v) {
return v != null && typeof v === 'object'
&& typeof v.session_id === 'string' && v.session_id.length > 0
&& typeof v.crew_member_id === 'string'
&& typeof v.name === 'string';
} Try / catch
try { return await getAssistantSession(); } catch (e) { if (/could not be resolved/.test(e.message)) { await reloadAssistantState(); } throw e; } Prevention
- Ensure the app's startup seeding hook completes before serving traffic (wait for readiness on deploy).
- Never manually NULL crew_members.session_id; if it is NULL, delete the row and let lazy seeding recreate it.
- Back up the database as a whole — restoring crew_members without sessions produces exactly this 500.
- Monitor logs for ensure_assistant_defaults exceptions on first request per user.
When it happens
Trigger: Calling GET /api/assistant/session on a fresh user whose startup seeding hook never ran and whose lazy ensure_assistant_defaults failed (e.g. DB write error, exception swallowed); a CrewMember row with is_default_assistant=1 exists but session_id is NULL because the Session insert was rolled back or a migration wiped it; DB recreated from a partial backup that kept crew rows but lost session rows.
Common situations: Fresh installs where the app was killed before the startup hook finished; SQLite file replaced/restored while the app runs; schema drift after upgrade where session_id column was added without backfill; concurrent first requests from the same new user racing the seed.
Related errors
- Assistant not available
- Assistant not found
- Failed to delete calendar
- Failed to list calendars
- Failed to create calendar
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/cb6cb742e1f4abbe.
Report an issue: GitHub.