Skip to content

feat(devx): isolated per-worktree Obsidian E2E vault wrapper#188

Merged
chhoumann merged 10 commits into
masterfrom
chhoumann/devx-worktree-vault-isolation
Jun 15, 2026
Merged

feat(devx): isolated per-worktree Obsidian E2E vault wrapper#188
chhoumann merged 10 commits into
masterfrom
chhoumann/devx-worktree-vault-isolation

Conversation

@chhoumann

@chhoumann chhoumann commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an isolated per-worktree Obsidian vault workflow so parallel worktree agents stop clobbering the single shared dev vault during runtime verification. Today every worktree races on the same vault's plugin symlink, data.json, and plugin:reload; this ports QuickAdd's solution and adapts it to PodNotes.

This is DevX tooling only — no plugin/runtime code changes, so no user-facing behavior, release, or migration impact.

What's included

  • scripts/provision-obsidian-e2e-vault.mjs — provisions .obsidian-e2e-vaults/podnotes-<worktree> (git-ignored), symlinks manifest.json + main.js only (PodNotes injects its CSS into the bundle, so there is no styles.css), and seeds a DEFAULT_SETTINGS-shaped data.json.
  • scripts/start-obsidian-e2e-instance.mjs — starts/reuses a private-HOME Obsidian instance bound to the worktree vault, disables Restricted Mode, and confirms PodNotes loaded via obsidian eval.
  • scripts/obsidian-e2e-cli.mjs — the obsidian:e2e wrapper: provision + start + run a command with vault=<worktree vault> and the private HOME already applied.
  • npm scripts: obsidian:e2e, provision:e2e-vault, start:e2e-obsidian.
  • .gitignore: ignore .obsidian-e2e-vaults.
  • AGENTS.md: documents both the shared dev-vault workflow (main checkout) and the isolated worktree workflow, including the export HOME=$PODNOTES_E2E_OBSIDIAN_HOME remap needed to point tests/e2e at the isolated instance.
  • Tests (15): ported provision + start-instance + CLI-parser unit tests, wired into vitest.config.ts. lint and format:check (Biome) widened to cover scripts/.

Why isolation holds

Vaults are always worktree-local under .obsidian-e2e-vaults/podnotes-<basename>; every CLI call pins HOME to a per-instance private profile, and the obsidian binary routes IPC by $HOME/.obsidian-cli.sock. Distinct worktrees get distinct vault paths and distinct instance IDs / HOMEs / sockets (sha256 of resolved worktree path + vault name), so two parallel agents can't collide. The shared dev vault is registered only in the real user HOME and is unreachable by these scripts.

--register-via (the one QuickAdd path that ran the CLI against the real user HOME) was intentionally dropped — the private-HOME instance model makes it unnecessary, and removing it eliminates the only real-HOME-touching code path.

How usage looks

npm run build                                   # produce root main.js + manifest.json
npm run obsidian:e2e -- eval code=app.vault.getName()
npm run obsidian:e2e -- eval code='Boolean(app.plugins.plugins.podnotes)'
npm run obsidian:e2e -- dev:errors

Verification

Runtime-verified end-to-end in an isolated worktree (this also proves isolation):

  • obsidian:e2e -- eval code=app.vault.getName()podnotes-devx-worktree-vault-isolation (not dev)
  • Boolean(app.plugins.plugins.podnotes)true; manifest.version2.16.0 (our bundle loaded)
  • app.vault.adapter.basePath → the worktree-local .obsidian-e2e-vaults/... path
  • The shared dev vault symlinks and the real ~/Library/.../obsidian.json were never touched (0 references to the worktree vault in the real registry; exactly 1 in the private HOME).

Gates run locally (Node 22, as CI): npm run lint, npm run format:check, npm run typecheck, npm run build, npm run test (271 passed) — all green.

Review

Self-reviewed with an automated multi-dimension review + 2 adversarial reviewers (correctness, isolation-safety, devex/portability, maintainability). Verdict: ship; isolation thesis confirmed; 0 blocking findings. Verification-integrity follow-ups were applied in this branch (gate scope widened to scripts/, CLI parser test restored, a real seed-vs-DEFAULT_SETTINGS drift guard added, --register-via removed).


Open in Devin Review

Port QuickAdd's per-worktree Obsidian vault isolation to PodNotes so parallel
worktree agents stop clobbering the shared dev vault on the plugin symlink,
data.json, and plugin:reload.

- scripts/provision-obsidian-e2e-vault.mjs: provision .obsidian-e2e-vaults/
  podnotes-<worktree>, symlink manifest.json + main.js from the worktree build
  (CSS is injected, so no styles.css), seed a DEFAULT_SETTINGS-shaped data.json.
- scripts/start-obsidian-e2e-instance.mjs: start/reuse a private-HOME Obsidian
  instance bound to the worktree vault; disable Restricted Mode; verify podnotes
  loaded via obsidian eval.
- scripts/obsidian-e2e-cli.mjs: obsidian:e2e wrapper (provision + start + run a
  command with vault=<worktree vault> and private HOME applied).
- npm scripts obsidian:e2e / provision:e2e-vault / start:e2e-obsidian; ignore
  .obsidian-e2e-vaults; AGENTS.md documents the shared dev vault (main checkout)
  and isolated worktree workflows (incl. the HOME remap for tests/e2e).
- Ported unit tests (provision, start, CLI parser) wired into vitest.config.ts;
  lint + format:check (biome) widened to cover scripts/.

Hardening from self-review:
- Dropped --register-via entirely: it ran the obsidian CLI against the real
  user HOME, the one path that could touch ~/Library obsidian.json / the shared
  vault. The private-HOME instance model makes it unnecessary; removing it also
  drops a dual same-named export trap and the readiness-marker duplication.
- Drift guard: a test now asserts the seeded data.json equals src/constants.ts
  DEFAULT_SETTINGS (JSON-serialized), so a new setting can't silently skew.

Verified end-to-end in this worktree: obsidian:e2e targets the worktree-local
vault (app.vault.getName() => podnotes-devx-worktree-vault-isolation,
Boolean(app.plugins.plugins.podnotes) => true, manifest 2.16.0); the shared dev
vault and the real ~/Library obsidian.json were never touched. Gates green:
lint, format:check, typecheck, build, test (271).
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: 411ce4c
Status: ✅  Deploy successful!
Preview URL: https://0723c7c3.podnotes.pages.dev
Branch Preview URL: https://chhoumann-devx-worktree-vaul.podnotes.pages.dev

View logs

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread vitest.config.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5328994540

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/start-obsidian-e2e-instance.mjs Outdated
Comment thread scripts/obsidian-e2e-cli.mjs Outdated
Add scripts/**/*.ts to tsconfig include so npm run typecheck (tsc --noEmit)
covers the ported wrapper unit tests, and enable allowJs so the tests' imports
of the sibling .mjs scripts resolve (their internals stay unchecked since
checkJs is off). Resolves the Devin review finding on PR #188.
…launch

Address Codex review on PR #188:
- start/provision main(): with --print-env, route the human status summary to
  stderr so stdout carries only the `export ...` lines. The documented
  `eval "$(... --print-env)"` flow previously tried to run "Prepared",
  "Vault", etc. as commands, leaving HOME/vault unset.
- obsidian-e2e-cli isInstanceReady(): skip the probe when no
  $HOME/.obsidian-cli.sock exists. The Obsidian CLI auto-launches on the first
  command when nothing is running, which could race the open -n launch and leave
  two processes on one private profile/vault.
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chhoumann

Copy link
Copy Markdown
Owner Author

@devin review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a4052e190

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/provision-obsidian-e2e-vault.mjs Outdated
Comment thread scripts/start-obsidian-e2e-instance.mjs Outdated
Address Codex re-review on PR #188:
- resolveProvisionOptions: default the vault root to the --worktree path (not the
  caller's cwd) so `--worktree /other/checkout` without --root stays
  worktree-local; parallel worktrees no longer share a root. Explicit --root
  still resolves against cwd. Adds a regression test.
- start-obsidian-e2e-instance: add an exported, non-launching isInstanceReady
  (socket check + probe) and use it in main() so a repeated
  `start:e2e-obsidian` reuses the live instance instead of `open -n`-ing a
  second app on the same private HOME/vault.
- obsidian-e2e-cli: import isInstanceReady from the start module (drop the
  duplicate copy and now-unused imports).
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chhoumann

Copy link
Copy Markdown
Owner Author

@devin review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af323e7b02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/obsidian-e2e-cli.mjs Outdated
…effect

Address Codex re-review on PR #188. When the isolated instance was already
running, ensureObsidianInstance skipped launch and only checked that some
PodNotes instance existed — it never reloaded after provisionVault relinked the
current main.js, so 'npm run build && npm run obsidian:e2e ...' could verify a
stale bundle.

Add an exported reloadPodNotes (obsidian plugin:reload id=podnotes) to the start
module and, on the reuse path, reload + re-verify before running the caller's
command. A freshly launched instance already loaded the current bundle, so it is
not reloaded. Validated: an in-memory probe set on the plugin is cleared after a
reused invocation, confirming the plugin is re-instantiated from disk.
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 535d44a987

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/start-obsidian-e2e-instance.mjs
…d launch

Address Codex re-review on PR #188. waitForInstanceReady probed
`obsidian vault=...` immediately after open -n, before $HOME/.obsidian-cli.sock
exists. Since the CLI auto-launches Obsidian when nothing is listening, that
first probe could start a second instance on the same private profile/vault and
race the one just opened.

Extract cliSocketPath/cliSocketExists helpers (shared with isInstanceReady) and
skip each probe in waitForInstanceReady until the socket appears. Validated with
a real cold start (kill instance + remove socket): one main Obsidian process,
correct worktree vault resolved, gates green.
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1f7e658bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/obsidian-e2e-cli.mjs
Address Codex re-review on PR #188. ensureObsidianInstance ran
trustVaultAndVerifyPodNotes (which waits for the plugin to be present) BEFORE
reloadPodNotes on the reuse path. If a reused instance held a broken
pre-rebuild bundle, that verify would time out and throw before the reload could
load the fixed main.js, wedging the rebuild-and-rerun loop.

Reorder: on reuse, reload first, then verify once (covering both paths). Removes
the redundant double-verify. Re-validated: an in-memory probe is still cleared
after a reused invocation (reload happens), gates green.
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c45008cca4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md
Comment thread scripts/start-obsidian-e2e-instance.mjs Outdated
… e2e run

Address Codex re-review on PR #188:
- start-obsidian-e2e-instance main(): the standalone start:e2e-obsidian reuse
  path skipped launch but never reloaded, so --print-env could hand the Vitest
  e2e suite a stale bundle after a rebuild. Now reload PodNotes when reusing a
  live instance (mirrors the obsidian:e2e wrapper), then wait + verify once.
- AGENTS.md: the --print-env recipe ran test:e2e (build && vitest) after starting
  the instance; provisioning also requires main.js to exist. Build first so the
  instance loads the current bundle. Validated start's reuse reload via the raw
  obsidian CLI (an in-memory probe is cleared after start:e2e-obsidian reuses).
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e42465ca19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/start-obsidian-e2e-instance.mjs
Address Codex re-review on PR #188. linkHostKeychains derives source from
process.env.HOME. After the documented 'export HOME=$PODNOTES_E2E_OBSIDIAN_HOME',
a subsequent obsidian:e2e/start:e2e-obsidian has process.env.HOME ===
options.obsidianHome, so source === destination: the mismatch branch unlinked the
real host-keychain symlink and recreated it pointing at itself, leaving the
private profile with a broken Keychains path (breaks Electron safeStorage).

Guard: return early when source and destination resolve to the same path. A
normal-HOME run still relinks (and recovers an already-corrupted profile).

Validated: reproduced the self-referential broken link with HOME remapped; after
the fix a normal-HOME run restores the real-keychains symlink and a remapped-HOME
run preserves it. Gates green.
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 609047475d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/start-obsidian-e2e-instance.mjs Outdated
Address Codex re-review on PR #188 (P3). start:e2e-obsidian --print-env verifies
the instance with --obsidian-bin but only printed vault/HOME exports, so the
Vitest harness (OBSIDIAN_BIN ?? "obsidian") could target a different binary.
toInstanceShellExports now also emits 'export OBSIDIAN_BIN=...' when a non-default
binary is used. Added a unit test; validated via --print-env (default => no
OBSIDIAN_BIN line; custom => present).
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 411ce4ce2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chhoumann chhoumann merged commit a8b7a4a into master Jun 15, 2026
2 checks passed
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