# OpenCode Session Internals This document contains findings about how OpenCode manages sessions, based on direct database investigation. Use this when debugging session-related issues in kugetsu. ## Database Location ```bash opencode db path # Returns: ~/.local/share/opencode/opencode.db ``` ## Session Table Schema ```sql CREATE TABLE `session` ( `id` text PRIMARY KEY, `project_id` text NOT NULL, `parent_id` text, -- Parent session ID (for forked sessions) `slug` text NOT NULL, -- Auto-generated adjective-animal name `directory` text NOT NULL, -- Working directory for session `title` text NOT NULL, `version` text NOT NULL, `share_url` text, `summary_additions` integer, `summary_deletions` integer, `summary_files` integer, `summary_diffs` text, `revert` text, `permission` text, -- JSON array of permission rules `time_created` integer NOT NULL, -- Unix timestamp in milliseconds `time_updated` integer NOT NULL, `time_compacting` integer, `time_archived` integer, `workspace_id` text ); ``` ## Session ID Format OpenCode session IDs follow the format: `ses_` Example: `ses_2b4eb7afbffezJwifgucdLRkt8` The ID appears to be generated using a timestamp-based algorithm with random components. Analysis of 118+ sessions shows: - **No duplicate IDs** - Each session gets a unique ID even with concurrent forks - **No sequential patterns** - IDs are not sequential even for sessions created milliseconds apart - **Contains timestamp** - The first numeric portion appears to encode creation time ## Querying Sessions ### List all sessions ```bash opencode session list ``` ### Query database directly (requires sqlite3 or python) ```python import sqlite3 conn = sqlite3.connect('/home/shoko/.local/share/opencode/opencode.db') cursor = conn.cursor() # Get all sessions cursor.execute('SELECT id, parent_id, slug, directory FROM session') # Get forked sessions (sessions with a parent) cursor.execute('SELECT id, parent_id FROM session WHERE parent_id IS NOT NULL') # Get sessions by directory cursor.execute("SELECT id, slug FROM session WHERE directory LIKE '%kugetsu%'") ``` ## Session Relationships ### Parent-Child Relationships When you run `opencode run --fork --session `, OpenCode: 1. Creates a NEW session with a unique ID 2. Sets the `parent_id` field to reference the parent session 3. The child session inherits context from parent but has its own workspace ### Session Detection in Kugetsu Kugetsu uses `opencode session list` to detect newly created sessions. The output format is: ``` ses_abc123def456 ses_xyz789... ``` Kugetsu's `cmd_start` workflow: 1. **Before fork**: List all sessions, store in array 2. **Fork**: Run `opencode run --fork --session ` 3. **After fork**: List sessions again 4. **Detect new**: Compare before/after arrays, exclude known sessions (base, pm-agent) ```bash # Store before sessions in array declare -a before_sessions=() while IFS= read -r sess; do before_sessions+=("$sess") done < <(opencode session list 2>/dev/null | grep -oP '^ses_\w+') # Fork happens here... # Find sessions not in before array while IFS= read -r sess; do # Skip base and pm-agent sessions [ "$sess" = "$base_session_id"" ] && continue [ "$sess" = "$pm_agent_session_id" ] && continue # Check if session existed before local existed_before=false for before_sess in "${before_sessions[@]}"; do if [ "$sess" = "$before_sess" ]; then existed_before=true break fi done if [ "$existed_before" = false ]; then new_session_id="$sess" break fi done < <(opencode session list 2>/dev/null | grep -oP '^ses_\w+') ``` ## Session Directories Each session has a `directory` field indicating its working directory: | Directory | Purpose | |-----------|---------| | `/home/shoko` | Base session, PM agent | | `/home/shoko/repositories/kugetsu` | Project sessions | | `~/.kugetsu/worktrees/` | Per-issue worktrees | ## Permissions Sessions have a `permission` field containing a JSON array: ```json [ {"permission": "question", "pattern": "*", "action": "deny"}, {"permission": "plan_enter", "pattern": "*", "action": "deny"}, {"permission": "plan_exit", "pattern": "*", "action": "deny"}, {"permission": "external_directory", "pattern": "*", "action": "allow"} ] ``` ### Common Permission Issues **Issue**: `permission requested: external_directory (/path/*); auto-rejecting` **Cause**: The session's `permission` field may be `NULL` or missing required rules. **Fix**: Update via SQLite: ```python import sqlite3 conn = sqlite3.connect('/home/shoko/.local/share/opencode/opencode.db') cursor = conn.cursor() PERMISSION_JSON = '[{"permission":"question","pattern":"*","action":"deny"},{"permission":"plan_enter","pattern":"*","action":"deny"},{"permission":"plan_exit","pattern":"*","action":"deny"},{"permission":"external_directory","pattern":"*","action":"allow"}]' cursor.execute("UPDATE session SET permission = ? WHERE id = ?", (PERMISSION_JSON, session_id)) conn.commit() ``` ## Known Issues & Solutions ### Session ID Collision (Issue #81) **Problem**: Forked sessions showing same ID as PM agent. **Investigation Results**: - OpenCode does NOT generate duplicate IDs (verified with 118+ sessions) - Database shows unique IDs even for concurrent forks - Issue is in kugetsu's session detection logic, not opencode **Solution**: Use array-based session detection (see above) instead of string/regex matching. ### Stale Permission NULL (Issue #36) **Problem**: PM agent cannot access directories despite permissions. **Root Cause**: Session created with `permission = NULL` in database. **Detection**: ```python cursor.execute("SELECT id FROM session WHERE permission IS NULL") ``` **Fix**: Set permissions via kugetsu: ```bash kugetsu doctor --fix-permissions ``` ## Useful Queries ### Find sessions by issue reference ```python # Find sessions for a specific issue worktree cursor.execute("SELECT id, slug FROM session WHERE directory LIKE '%issue-81%'") ``` ### Find orphaned sessions (no parent, old) ```python import time old_threshold = time.time() - (30 * 24 * 60 * 60) # 30 days ago cursor.execute("""SELECT id, slug, directory, time_created FROM session WHERE parent_id IS NULL AND time_created < ? ORDER BY time_created""", (old_threshold * 1000,)) ``` ### Count sessions per project ```python cursor.execute("""SELECT project_id, COUNT(*) as cnt FROM session GROUP BY project_id ORDER BY cnt DESC""") ``` ## Debugging Tips 1. **Check current sessions**: `opencode session list` 2. **Check database**: `opencode db "SELECT id, parent_id, slug FROM session ORDER BY time_created DESC LIMIT 10"` 3. **Verify permissions**: Check if `permission` field is NULL or valid JSON 4. **Check directory**: Ensure session directory exists and is accessible 5. **Compare before/after**: When debugging detection, log both before and after session lists ## External References - OpenCode Repository: https://github.com/opencode-ai/opencode - Session Management: Uses SQLite with unique constraint on `id` column - Fork Operation: Sets `parent_id` to establish relationship