Skip to content

fix(project): relocate session history when a worktree is renamed or moved - #45133

Open
neohiro wants to merge 5 commits into
anomalyco:devfrom
neohiro:fix/relocate-project-history
Open

fix(project): relocate session history when a worktree is renamed or moved#45133
neohiro wants to merge 5 commits into
anomalyco:devfrom
neohiro:fix/relocate-project-history

Conversation

@neohiro

@neohiro neohiro commented Aug 26, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #23248, closes #34737. Towards #44256, #44538, #29703, #27822.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Renaming or moving a project folder outside opencode orphaned every session (empty UI history, ghost Desktop entry whose Delete failed). This PR:

  1. Auto-relink - when a stored worktree no longer exists on disk and fromDirectory() resolves the same project identity at a new path, all references migrate: session.directory/path, plus message/part/todo/event payloads, rewritten by an encoding-preserving splicer that handles plain, forward-slashed, drive-less and JSON-escaped (1x/2x) forms while leaving recorded content (diffs, source quotes, sibling folders, other users' paths) untouched.
  2. Rename from the app - project.update accepts worktree; changing it relocates history the same way (this is what the Desktop Edit-project dialog will call).
  3. Delete projects - new DELETE /project/:id?mode=cascade|detach: cascade removes registration, directories, sessions, messages/parts/todos/event aggregates; global project is protected.
  4. docs/project-rename-move-reliability.md documents the failure model measured on a real affected install (~6.9k payload fields across 4 encodings had to move together).

Follow-up (small, after codegen): Edit dialog gains a Folder field bound to update({worktree}), project context menu gains Delete… with a history-loss confirmation calling project.remove. Server surface for both is complete here.

How did you verify your code works?

  • Recovered a real affected install using exactly these migrations before writing them up.
  • bun test test/project/relocation-paths.test.ts: 18 tests green (escape depths incl. triple-JSON, moves shallower/deeper, case handling, unicode boundaries, g-flag statefulness regression, sibling/other-user guards, 1MB perf smoke ~14ms).
  • DB integrity_check ok / foreign_key_check clean after migration on the affected database.
  • Typecheck of Effect/drizzle wiring lands with CI (authored against core/session/sql + core/event/sql schemas as of dev).

Screenshots / recordings

No UI pixels changed in this PR; the follow-up GUI wiring PR will include recordings of Rename and the Delete confirmation.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…moved

Renaming or moving a project folder outside of opencode (file explorer, mv,
IDE refactor) orphaned every stored reference to the old absolute path:
sessions disappeared from the UI and Desktop kept a ghost project whose
delete call failed server-side.

- add relocation module with an encoding-preserving path splicer
  (plain / JSON-escaped at two depths / drive-less forms; safe against
  sibling folders, other users, and recorded content such as diffs)
- migrate session.directory/path plus message, part, todo and event
  payload text in one transaction when a missing worktree re-resolves
  to the same project identity during fromDirectory()
- document the failure model and migration design (docs/)

Field evidence: recovered a real affected install where ~6.9k payload
fields across four encodings had to move together; naive equality only
matched a handful (anomalyco#23248, anomalyco#34737, anomalyco#44256, anomalyco#44538).
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

- drop dead leadingGroupOffset / unused rewriteCandidates helper
- unicode-safe boundaries (\p{L}\p{N} guards, u flag): glued unicode
  neighbors can no longer bypass the bare-form guard; ReDoS-safety noted
- throw on identical locations (case/separator-insensitive) - nothing to
  relocate; callers degrade instead of no-op scanning
- relocation scoping tightened to sessions actually keyed to the old
  location (was: whole project); sessions metric now reflects migrated rows
- call site degrades gracefully: a failed migration logs and continues,
  it can no longer fail instance startup
- docs: failure semantics + reference implementation pointers

tests: 18/18 (adds triple-depth payload, g-flag statefulness regression,
unicode guard, trailing/mixed separators, identical-location throws,
1MB perf smoke ~14ms)
@neohiro

neohiro commented Aug 26, 2026

Copy link
Copy Markdown
Author

Audit/hardening round pushed (38c817f):

  • removed dead helpers (leadingGroupOffset, rewriteCandidates)
  • unicode-safe boundaries: \p{L}\p{N} guards + u flag - glued unicode neighbors can no longer slip past the bare-form guard; ReDoS-safety documented (single-class quantifiers between literals, no alternation backtracking)
  • createPathRewriter now throws on identical locations (case/separator-insensitive) instead of silently no-op scanning
  • migration scoping tightened: only sessions actually keyed to the old location are migrated/used to scope events & todos (was: whole project)
  • call site is fail-soft: a failed relocation logs and continues, it can no longer fail instance startup
  • docs updated to match implementation

Tests grew 12 → 18 (all green via bun test test/project/relocation-paths.test.ts): triple-JSON depth, g-flag statefulness regression, unicode guard, trailing/mixed separators, identical-location throws, 1MB perf smoke (~14ms).

- project.update accepts worktree: when it points at a new location the
  stored history (sessions, messages, parts, todos, events) migrates via
  the relocation splicer, so Right-click > Edit > folder change in the
  Desktop keeps every session visible (anomalyco#23248, anomalyco#34737)
- new DELETE /project/:id with mode=cascade|detach removes registration,
  directories and (cascade) all sessions + event aggregates; global
  project and unknown ids fail with ProjectNotFoundError
- httpapi group + handler wiring for both operations

GUI follow-up lands after `bun run codegen` regenerates sdk clients:
Edit dialog gains a Folder field bound to update({worktree}), project
context menu gains Delete... with a history-loss confirmation dialog.
@neohiro

neohiro commented Aug 26, 2026

Copy link
Copy Markdown
Author

Added rename-via-update (worktree field triggers full history relocation) and DELETE /project/:id?mode=cascade|detach (bf6401d). Desktop wiring: Edit dialog Folder field + context-menu Delete with confirmation once \�un run codegen\ regenerates sdk clients - happy to push that immediately if maintainers want it in this same PR.

@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Aug 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

- Edit dialog gains a Folder field: changing it sends worktree through
  project.update, which relocates all stored history server-side
- Danger zone with two-step Delete confirmation (cascade removes the
  project plus every session and its history) calling project.remove
- sdk client: worktree param on update + hand-added remove method
  (bun run codegen will normalize these gen files)
- en locale strings for folder/delete flows
@neohiro

neohiro commented Aug 26, 2026

Copy link
Copy Markdown
Author

GUI wiring pushed (3bca359): Edit Project dialog now has a Folder field (rename-with-relocation via project.update({worktree})) and a two-step Delete project danger action (project.remove({mode:"cascade"}), removes all session history after confirmation). sdk.gen.ts updated by hand to match the new endpoints; bun run codegen will keep the gen files canonical.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project path is not updated after moving project directory (opens old deleted path) Sessions become orphaned when project directory is renamed

1 participant