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.
248 lines
7.3 KiB
Markdown
248 lines
7.3 KiB
Markdown
# 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_<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
|
|
|
|
```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 <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)
|
|
|
|
```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/<issue-ref>` | 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
|