8. State & Edge Cases
Status Lifecycles
Session Status
idle → active (message sent) → streaming (response in progress) → idle
↘ error → idle
No explicit session close. Sessions become inactive when CLI process exits or times out.
Onboarding Research Status
pending → researching → completed
↘ failed (can retry)
Stored in company_profiles.research_status.
Execution Status
running → completed
→ failed
→ timeout (exceeded timeout_ms)
→ stale (detected as orphaned)
→ cancelled (manual cancellation)
Applies to: execution_history.status, executions.status.
Stale detection: Scheduler checks for executions stuck in 'running' beyond their timeout and marks them 'stale'.
Cloud Mount Health
unmounted → mounting → mounted
↘ degraded → remounting → mounted
↘ degraded (persistent failure)
Stored in cloud_mounts.health_status.
Cloud Connection Status
pending → authorized (OAuth successful)
→ revoked (user or admin revoked)
Stored in cloud_connections.status.
Schedule State
enabled (cron active) ↔ disabled (cron paused)
schedules.enabled is an integer (0/1). Toggle via /api/scheduler/enable/:name and /disable/:name.
Edge Cases
Authentication
| Case |
Behavior |
| JWT_SECRET not set |
Falls back to hardcoded dev default (insecure) |
| JWT with deleted user |
Token validates but user lookup fails → 401 |
| Concurrent logins |
Both sessions valid (no single-session enforcement) |
| Browser data cleared |
Session lost, user must re-login. Filesystem sessions survive. |
| Container restart |
JWT_SECRET may change (if env-based), invalidating all tokens |
Chat Sessions
| Case |
Behavior |
| Claude CLI crashes mid-response |
WebSocket error event, partial response shown |
| WebSocket disconnect |
Client auto-reconnects, new CLI process spawned |
| Very large response |
Streamed token-by-token, no size limit enforced |
| Concurrent messages from same user |
Each spawns separate CLI process |
| API key invalid or expired |
Error surfaced in chat: "Claude is not configured" |
| File system full |
CLI write operations fail, error in response |
File Operations
| Case |
Behavior |
| File exceeds size limit |
413 response, file rejected |
| Path traversal attempt |
safePath() blocks with error |
| Duplicate filename upload |
Overwrites existing file (no versioning) |
| Delete system file (CLAUDE.md) |
UI prevents deletion via isCloudItem() / canDeleteFolder() checks |
| Concurrent file writes |
SQLite busy_timeout (10s) handles contention, filesystem has no lock |
Scheduling
| Case |
Behavior |
| Duplicate execution (same slot) |
UNIQUE(schedule_id, scheduled_for) constraint prevents |
| Schedule fires while previous still running |
New execution starts (no queue, no backpressure) |
| Server restart during execution |
Execution stays 'running', eventually marked 'stale' |
| Invalid cron expression |
Validation at creation time, rejected with 400 |
| Timezone edge cases |
Handled by cron-parser with timezone support |
| NULL cron_expression |
On-demand task only, never auto-fires |
Cloud Drives
| Case |
Behavior |
| OAuth token expired |
Automatic refresh via stored refresh_token |
| Refresh token revoked |
Connection marked 'revoked', manual re-auth required |
| rclone process dies |
Mount health → 'degraded', auto-remount attempted |
| Network timeout |
RCLONE_RC_TIMEOUT_MS (default 60s), error returned |
| Large file download |
Streamed, no memory buffering |
| Concurrent mount operations |
rclone handles internally |
Database
| Case |
Behavior |
| Database corruption |
PRAGMA integrity_check on startup, warn in logs |
| Concurrent writes |
busy_timeout = 10000 waits up to 10s for lock |
| Database file missing |
Auto-created with full schema from init.sql |
| Migration failure |
Error logged, server may start in degraded state |
RESET_DATABASE=true |
Full schema rebuild (destroys all data) |
Admin Operations
| Case |
Behavior |
| Delete only admin user |
TBD -- no protection against this currently |
| Delete user with active sessions |
CASCADE deletes session records, CLI process orphaned |
| Change own admin status |
TBD -- UI may not prevent self-demotion |
Empty States
| Screen |
Empty State |
| Chat |
Welcome message with suggested prompts |
| File Tree |
"No files" with upload button CTA |
| Skills |
Empty list with "Create skill" button |
| Schedules |
Empty list with "Create schedule" button |
| Meetings |
No active meeting indicator |
| Git |
"No repository" or "No changes" |
| Analytics |
"No data" with refresh button |
| Users (admin) |
First user always exists |
Loading States
| Screen |
Loading Pattern |
| Chat response |
Token-by-token streaming, animated dots |
| File tree |
Skeleton loader |
| Skill list |
Spinner |
| Schedule list |
Spinner |
| Analytics reports |
Section-by-section loading |
| Cloud file browsing |
Spinner with directory path |
| Onboarding research |
Step-by-step progress messages |
Error States
| Error |
User-Facing Message |
Recovery |
| Claude not configured |
"Claude is not configured" banner |
Setup form |
| API key invalid |
"Invalid API key" in chat |
Reconfigure in settings |
| Database error |
500 error |
Restart container |
| File upload too large |
"File exceeds maximum size" |
Reduce file size |
| Network error |
"Connection lost" |
Auto-reconnect attempt |
| Permission denied (non-admin) |
403 response |
Contact admin |
| WebSocket failure |
"Reconnecting..." banner |
Auto-retry |
Race Conditions
| Scenario |
Mitigation |
| Two users editing CLAUDE.md |
Last write wins (no locking) |
| Schedule fires during server shutdown |
Execution stays 'running', marked 'stale' on restart |
| Multiple file uploads to same path |
Last write wins (filesystem) |
| Concurrent API key validation |
bcrypt is thread-safe, no issue |
| Stats refresh triggered while running |
stats_refresh_log status check prevents duplicate |
| OAuth callback arrives after timeout |
Connection stays 'pending' until successful callback |
Timezone Handling
- Schedules: Stored timezone per schedule, evaluated by
cron-parser
- Timestamps: All database timestamps are UTC (ISO 8601 strings or
CURRENT_TIMESTAMP)
- Display: Client-side conversion to local timezone
- Logs: UTC timestamps in JSONL files