Files
kugetsu/docs/opencode-session-internals.md
shokollm 1f001fd057 docs: add opencode session internals documentation
Document findings from database investigation:
- Session table schema with all fields explained
- Session ID format and generation (unique, no duplicates)
- Parent-child relationships for forked sessions
- Session detection logic used by kugetsu
- Permission structure and common issues
- SQL queries for debugging session problems
- Known issues and solutions (from #81, #36)

This document helps future debugging of session-related issues
without having to investigate opencode internals directly.
2026-04-02 00:07:18 +00:00

7.3 KiB

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

opencode db path
# Returns: ~/.local/share/opencode/opencode.db

Session Table Schema

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_<base62_chars>

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

opencode session list

Query database directly (requires sqlite3 or 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 <parent_id>, 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 <parent>
  3. After fork: List sessions again
  4. Detect new: Compare before/after arrays, exclude known sessions (base, pm-agent)
# 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/<issue-ref> Per-issue worktrees

Permissions

Sessions have a permission field containing a JSON array:

[
  {"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:

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:

cursor.execute("SELECT id FROM session WHERE permission IS NULL")

Fix: Set permissions via kugetsu:

kugetsu doctor --fix-permissions

Useful Queries

Find sessions by issue reference

# 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)

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

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