Skip to content

Migrate worktree mappings to session metadata#514

Merged
shantur merged 5 commits into
devfrom
metadata-storage
Jun 1, 2026
Merged

Migrate worktree mappings to session metadata#514
shantur merged 5 commits into
devfrom
metadata-storage

Conversation

@shantur
Copy link
Copy Markdown
Collaborator

@shantur shantur commented May 31, 2026

Summary

  • Upgrade @opencode-ai/sdk to 1.15.13 and adapt UI types for the current session/diff API surface.
  • Add a session metadata store helper that safely updates OpenCode session metadata via read-merge-full-replace while preserving non-CodeNomad metadata.
  • Migrate CodeNomad worktree assignments from .codenomad/worktreeMap.json into metadata.codenomad.worktreeSlug, with legacy fallback, stale-entry pruning, and server-side deletion of the legacy map once empty.
  • Add .codenomad/background_processes/ to CodeNomad-managed .git/info/exclude entries.

Details

  • New sessions and worktree reassignment now persist explicit session worktree assignments in OpenCode session metadata under:
    metadata.codenomad.worktreeSlug
  • Worktree resolution now prefers session metadata, then falls back to legacy parentSessionWorktreeSlug, then root.
  • Legacy migration runs after session/map loading; after the full session list is known, missing legacy session IDs are treated as stale and pruned.
  • The UI no longer uses defaultWorktreeSlug; the fallback is root.
  • writeWorktreeMap deletes .codenomad/worktreeMap.json server-side when no legacy parent-session mappings remain.

Validation

  • npm run typecheck --workspace @codenomad/ui
  • npm run typecheck --workspace @neuralnomads/codenomad

shantur added 3 commits May 31, 2026 18:43
Upgrade @opencode-ai/sdk to 1.15.13 so the UI can use the latest OpenCode client surface, including session metadata support.

Preserve metadata returned by session list/create/children/fork flows and update the session diff type to SnapshotFileDiff after the SDK removed the older FileDiff export.

Validated with npm run typecheck --workspace @codenomad/ui.
Add the project-local background process storage directory to the CodeNomad-managed .git/info/exclude entries so generated command output and process indexes stay local.

This keeps the behavior aligned with the existing worktree metadata ignore setup and prevents accidental tracking in repositories without a root .gitignore entry for .codenomad/.

Validated with npm run typecheck --workspace @neuralnomads/codenomad.
Move CodeNomad's parent-session worktree assignment out of .codenomad/worktreeMap.json and into OpenCode session metadata under the codenomad namespace. The UI now resolves worktrees from session metadata first, falls back to legacy mappings during migration, and writes future changes through a session metadata helper that fetches latest metadata before full replacement so unrelated metadata is preserved.

Legacy map entries are migrated after sessions load, stale session IDs are pruned once the session list is known, and the server deletes worktreeMap.json when no parent-session mappings remain. Deleted worktrees also move affected sessions back to the root worktree metadata assignment.

Session update events now preserve metadata in the local session store. Validated with npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 31, 2026

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 31, 2026

Gatekeeper review for latest PR state.

Assumption: the request is for a ruthless review only, not for me to patch the PR in this webhook run.

Findings:

  1. Blocking: stale legacy mappings are not reliably pruned when the first migration starts before the full session list is loaded.

packages/ui/src/stores/worktrees.ts:170 starts migrateLegacyWorktreeMapToSessionMetadata(instanceId) from ensureWorktreeMapLoaded() without pruneMissingSessions. Later, fetchSessions() calls the same function with { pruneMissingSessions: true }, but packages/ui/src/stores/worktrees.ts:334-335 immediately returns the already-running promise. If the first migration is in flight, the prune-enabled pass is skipped, so entries for missing legacy session IDs survive even after the full session list is known. This contradicts the PR behavior claim that stale legacy session IDs are pruned after the full session list is known.

Suggested fix: make the migration pass option-aware, or do a second explicit stale-prune pass after session hydration that is not coalesced behind the non-pruning migration.

  1. High: deleting a parent session can skip legacy map cleanup because cleanup now tries to update metadata on a session that was already deleted.

packages/ui/src/stores/session-api.ts:579-624 removes the session from local state after session.delete, then calls removeParentSessionMapping(). That function resolves the current slug from state and calls setSessionWorktreeSlugWithClient() before removing the legacy map entry (packages/ui/src/stores/worktrees.ts:307-313). For a session that has already been deleted server-side, session.update is expected to fail; because the caller catches the whole removeParentSessionMapping() promise, removeLegacyParentSessionMapping() never runs. This leaves .codenomad/worktreeMap.json entries behind until some later prune happens, and that later prune is itself affected by finding #1.

Suggested fix: for deleted parent sessions, remove only the legacy mapping, or perform legacy cleanup in a finally path that does not depend on metadata update succeeding. If metadata cleanup is needed, do it before session.delete and still ensure legacy map deletion is independent.

  1. Medium: session creation in a non-root worktree may persist metadata through the wrong client.

In createSession(), the session is created through the active worktree client, but setWorktreeSlugForParentSession() computes currentSlug by reading metadata/legacy map before metadata exists (packages/ui/src/stores/worktrees.ts:297-303). For a new session with no prior metadata and no legacy entry, that resolves to root, so the metadata update is sent through the root worktree proxy rather than the worktree where the session was just created. If OpenCode scopes session lookup/update by x-opencode-directory, this can fail or update the wrong project scope, leaving new non-root sessions without metadata.codenomad.worktreeSlug.

Suggested fix: allow setWorktreeSlugForParentSession() to accept the known current/source slug, or add a creation-specific helper that writes metadata with the same client used to create the session.

Quality notes:

  • packages/ui/src/stores/session-api.ts is now about 926 lines, above the project’s warning threshold. This PR does not need to refactor it, but future changes in this area should split session lifecycle, loading, and mutation concerns.
  • The metadata helper uses a sensible read-merge-full-replace strategy and preserves unknown metadata.codenomad fields; that is the right direction.

Validation attempted:

  • git diff --check origin/dev...HEAD: passed.
  • npm run typecheck --workspace @codenomad/ui: could not complete in this checkout because dependencies/types are missing, starting with Cannot find module '@opencode-ai/sdk/v2/client', solid-js, and Node test/type modules.
  • npm run typecheck --workspace @neuralnomads/codenomad: could not complete because @types/node is missing (Cannot find type definition file for 'node').

Gatekeeper verdict: request changes before merge. The migration direction is good, but the coalesced migration options and cleanup ordering can leave legacy mappings stale and can fail the key non-root session creation path this PR is trying to make reliable.

--
Yours,
CodeNomadBot

@github-actions
Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/26721329112

Artifacts expire in 7 days.
Artifacts:

  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-tauri-linux
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-tauri-macos
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-tauri-windows
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-electron-macos
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-tauri-macos-arm64
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-electron-windows
  • pr-514-ac0a5ffffc95a67bba9a149cd0bbca820dec387d-electron-linux

Ensure the stale legacy mapping prune runs after session hydration even when an earlier non-pruning migration is already in flight. This prevents old worktreeMap.json entries for missing sessions from surviving after the full session list is known.

Keep deleted-session legacy cleanup independent from metadata updates by pruning legacy map entries directly after session deletion. Also pass the known source worktree when tagging newly created sessions so non-root session metadata is written through the client that created the session.

Validated with npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented Jun 1, 2026

@CodeNomadBot

Check again now

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented Jun 1, 2026

Follow-up gatekeeper review for latest PR state (509aea46).

Assumption: “Check again now” means re-review the updated PR and verify whether the prior gatekeeper findings were addressed.

Findings:

No blocking findings in the updated diff.

Prior findings re-check:

  1. Resolved: stale legacy map pruning is no longer coalesced behind a non-pruning migration.

fetchSessions() now runs migration and then calls pruneStaleLegacyWorktreeMapEntries() after session hydration. pruneStaleLegacyWorktreeMapEntries() waits for any in-flight migration before pruning missing session IDs, so the race I called out is addressed.

  1. Resolved: deleted parent-session cleanup no longer depends on updating metadata for an already-deleted session.

deleteSession() now calls removeLegacyParentSessionMapping() directly for deleted parent sessions, so legacy map removal is independent from session.update succeeding.

  1. Resolved: new non-root session metadata is written through the known source worktree client.

createSession() now passes { currentSlug: worktreeSlug } into setWorktreeSlugForParentSession(), so the metadata write uses the same worktree slug used to create the session.

Quality notes:

  • Minor cleanup opportunity: removeParentSessionMapping is still imported in packages/ui/src/stores/session-api.ts but is no longer used there. This is not blocking with the current tsconfig, but it is dead import noise.
  • packages/ui/src/stores/session-api.ts is about 931 lines, still above the project’s warning threshold. I would not block this PR on that, but future work should split it.

Validation attempted:

  • git diff --check origin/dev...HEAD: passed.
  • npm run typecheck --workspace @codenomad/ui: could not complete in this checkout because dependencies/types are missing, starting with Cannot find module '@opencode-ai/sdk/v2/client', solid-js, and Node test/type modules.
  • npm run typecheck --workspace @neuralnomads/codenomad: could not complete because @types/node is missing (Cannot find type definition file for 'node').

Gatekeeper verdict: the previously blocking issues are fixed. I do not see a reason to hold the PR based on this follow-up review, subject to CI/typecheck passing in a dependency-complete environment.

--
Yours,
CodeNomadBot

Drop the stale removeParentSessionMapping import from the session API store after deleted-session cleanup moved to legacy-only map pruning.

Validated with npm run typecheck --workspace @codenomad/ui.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/26743926225

Artifacts expire in 7 days.
Artifacts:

  • pr-514-509aea4610f4dac590f582a47f05436a961cc5fd-electron-windows
  • pr-514-509aea4610f4dac590f582a47f05436a961cc5fd-electron-linux

@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented Jun 1, 2026

@CodeNomadBot

Now?

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented Jun 1, 2026

Follow-up check for latest PR state (e1b5eb26).

Assumption: “Now?” means verify the newest push after the prior review note.

Result:

No new findings.

What changed since my last review:

  • e1b5eb26 chore(ui): remove unused worktree import removes the unused removeParentSessionMapping import from packages/ui/src/stores/session-api.ts.
  • The only code delta since 509aea46 is that import cleanup.
  • The prior blockers remain resolved.

Validation attempted:

  • git diff --check origin/dev...HEAD: passed.
  • npm run typecheck --workspace @codenomad/ui: still cannot complete in this checkout because dependency/type packages are unavailable, starting with missing @opencode-ai/sdk/v2/client, solid-js, and Node test/type modules.
  • npm run typecheck --workspace @neuralnomads/codenomad: still cannot complete because @types/node is missing.

Residual note:

  • packages/ui/src/stores/session-api.ts is still large at about 930 lines. Not a blocker for this PR.

Gatekeeper verdict: clear from my review. I do not see a reason to hold the PR, subject to CI/typecheck passing in a dependency-complete environment.

--
Yours,
CodeNomadBot

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/26744157185

Artifacts expire in 7 days.
Artifacts:

  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-tauri-macos
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-electron-macos
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-tauri-linux
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-tauri-windows
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-tauri-macos-arm64
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-electron-windows
  • pr-514-e1b5eb267d68cd5eb7c06cd8eb1cef72ff1be994-electron-linux

@shantur shantur merged commit 873235e into dev Jun 1, 2026
11 checks passed
@shantur shantur deleted the metadata-storage branch June 1, 2026 09:16
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.

1 participant