Skip to content

fix: fix fork cleanup title matching#16

Merged
kuitos merged 2 commits intomainfrom
fix/fork-cleanup-title-match
Apr 15, 2026
Merged

fix: fix fork cleanup title matching#16
kuitos merged 2 commits intomainfrom
fix/fork-cleanup-title-match

Conversation

@kuitos
Copy link
Copy Markdown
Owner

@kuitos kuitos commented Apr 14, 2026

What changed

This changes fork-session cleanup in bin/opencode-memory to match forked sessions by title instead of deleting whichever newer in-scope session appears after a maintenance fork.

Why

The previous cleanup path still relied on recency heuristics. If another normal session appeared in the same directory after extraction or auto-dream started, cleanup could delete that real session instead of the forked maintenance session.

Root cause

OpenCode forked sessions on this machine expose a stable (fork #N) title suffix, but the wrapper was not using that signal. Cleanup was selecting from newly seen sessions by time rather than proving that a candidate was actually the fork child for the current parent session.

Implementation

  • read the parent session title from the local opencode.db
  • search the local session DB for in-scope sessions created after the fork start time
  • only treat sessions whose title matches <parent title> (fork #N) as cleanup candidates
  • delete only when there is exactly one match; otherwise skip cleanup safely
  • add wrapper tests covering newer normal sessions, multiple matching fork sessions, and missing parent-title cases

Validation

  • bun test test/opencode-memory.test.ts
  • bash -n bin/opencode-memory

Notes

A full bun test run in the sandbox still hits existing EPERM failures when unrelated tests try to write under ~/.claude/projects/...; this PR does not change that behavior.

@kuitos kuitos marked this pull request as ready for review April 14, 2026 16:09
@kuitos kuitos changed the title [codex] fix fork cleanup title matching fix: fix fork cleanup title matching Apr 14, 2026
@kuitos kuitos requested a review from Copilot April 14, 2026 16:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the bin/opencode-memory wrapper’s fork-session cleanup to identify forked maintenance sessions by matching the fork title suffix (<parent title> (fork #N)) from opencode.db, avoiding accidental deletion of unrelated newer sessions in the same directory.

Changes:

  • Add opencode.db helpers to resolve the parent session title and to locate a unique fork cleanup candidate by title + scope + creation time.
  • Replace recency-based fork cleanup selection with title-based matching and “delete only on exactly-one match” safety behavior.
  • Extend wrapper tests to cover newer normal sessions, multiple matching forks, and missing parent-title scenarios.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 4 comments.

File Description
bin/opencode-memory Switch fork cleanup logic to DB-backed parent-title matching and unique-candidate deletion.
test/opencode-memory.test.ts Add DB seeding and new test cases validating fork cleanup selection behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1106 to +1110
if [ "\${1:-}" = "run" ] && [ "\${2:-}" = "-s" ]; then
now_ms=$(( $(date +%s) * 1000 ))
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_target', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
mkdir -p "$CLAUDE_CONFIG_DIR/transcripts"
printf '{"type":"user","content":"fork"}\n{"type":"tool_use","content":""}\n' > "$CLAUDE_CONFIG_DIR/transcripts/ses_fork_cleanup_target.jsonl"
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fake opencode script depends on the sqlite3 CLI for DB writes. If the environment lacks sqlite3, this test will fail unrelated to the wrapper logic. Prefer performing these inserts via an inline python3 snippet (sqlite3 module) or seeding the DB from the test runner.

Copilot uses AI. Check for mistakes.
Comment thread test/opencode-memory.test.ts Outdated
fi
if [ "\${1:-}" = "run" ] && [ "\${2:-}" = "-s" ]; then
now_ms=$(( $(date +%s) * 1000 ))
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_target', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests rely on the external sqlite3 CLI inside the fake opencode script to mutate opencode.db. This adds a new system dependency (and potential flake) that isn’t required by the wrapper itself (which uses Python’s sqlite3 module). Prefer inserting rows via an inline python3 snippet (matching the wrapper dependency) or pre-seeding the DB from the test process so the tests don’t require sqlite3 to be installed.

Suggested change
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_target', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
python3 - "$DB_PATH" "$now_ms" <<'PY'
import sqlite3
import sys
db_path = sys.argv[1]
now_ms = int(sys.argv[2])
conn = sqlite3.connect(db_path)
try:
conn.execute(
"INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES (?, NULL, ?, ?, ?, ?)",
("ses_fork_cleanup_target", ${JSON.stringify(root)}, "Wrapped Main Task (fork #1)", now_ms, now_ms),
)
conn.commit()
finally:
conn.close()
PY

Copilot uses AI. Check for mistakes.
Comment thread test/opencode-memory.test.ts Outdated
Comment on lines +948 to +949
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_target', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_parallel_real', NULL, '${root}', 'Parallel normal session', $((now_ms + 1000)), $((now_ms + 1000)));"
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fake opencode implementation uses the sqlite3 CLI to insert session rows. If sqlite3 is missing in CI/dev environments, these tests will fail even though the wrapper only requires python3. Consider switching this insertion to an inline python3/sqlite3-module script or pre-seeding the needed rows from the test runner to remove the sqlite3 binary dependency.

Suggested change
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_target', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_parallel_real', NULL, '${root}', 'Parallel normal session', $((now_ms + 1000)), $((now_ms + 1000)));"
python3 <<PY
import sqlite3
db_path = "${dbPath}"
root = "${root}"
now_ms = int("${'${now_ms}'}")
conn = sqlite3.connect(db_path)
conn.execute(
"INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)",
("ses_fork_cleanup_target", None, root, "Wrapped Main Task (fork #1)", now_ms, now_ms),
)
conn.execute(
"INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)",
("ses_parallel_real", None, root, "Parallel normal session", now_ms + 1000, now_ms + 1000),
)
conn.commit()
conn.close()
PY

Copilot uses AI. Check for mistakes.
Comment thread test/opencode-memory.test.ts Outdated
Comment on lines +1033 to +1035
now_ms=$(( $(date +%s) * 1000 ))
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_one', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_two', NULL, '${root}', 'Wrapped Main Task (fork #2)', $((now_ms + 1000)), $((now_ms + 1000)));"
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test’s fake opencode uses the sqlite3 CLI to create fork sessions. That introduces an extra system dependency for the test suite. Using python3 (already a wrapper dependency) to write the rows, or pre-seeding the DB with appropriately-timestamped sessions, would make the tests more portable and less flaky.

Suggested change
now_ms=$(( $(date +%s) * 1000 ))
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_one', NULL, '${root}', 'Wrapped Main Task (fork #1)', $now_ms, $now_ms);"
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES ('ses_fork_cleanup_two', NULL, '${root}', 'Wrapped Main Task (fork #2)', $((now_ms + 1000)), $((now_ms + 1000)));"
python3 - "$DB_PATH" "${root}" <<'PY'
import sqlite3
import sys
import time
db_path = sys.argv[1]
root = sys.argv[2]
now_ms = int(time.time() * 1000)
conn = sqlite3.connect(db_path)
try:
conn.execute(
"INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)",
("ses_fork_cleanup_one", None, root, "Wrapped Main Task (fork #1)", now_ms, now_ms),
)
conn.execute(
"INSERT OR REPLACE INTO session (id, parent_id, directory, title, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)",
("ses_fork_cleanup_two", None, root, "Wrapped Main Task (fork #2)", now_ms + 1000, now_ms + 1000),
)
conn.commit()
finally:
conn.close()
PY

Copilot uses AI. Check for mistakes.
@kuitos kuitos merged commit f0ce81d into main Apr 15, 2026
1 check passed
@kuitos kuitos deleted the fix/fork-cleanup-title-match branch April 15, 2026 04:50
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.6.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants