feat(devx): isolated per-worktree Obsidian E2E vault wrapper#188
Conversation
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).
Deploying podnotes with
|
| Latest commit: |
411ce4c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0723c7c3.podnotes.pages.dev |
| Branch Preview URL: | https://chhoumann-devx-worktree-vaul.podnotes.pages.dev |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
|
@devin review |
There was a problem hiding this comment.
💡 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".
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).
|
@codex review |
|
@devin review |
There was a problem hiding this comment.
💡 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".
…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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
…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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
… 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).
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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).
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
Adds an isolated per-worktree Obsidian vault workflow so parallel worktree agents stop clobbering the single shared
devvault during runtime verification. Today every worktree races on the same vault's plugin symlink,data.json, andplugin: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), symlinksmanifest.json+main.jsonly (PodNotes injects its CSS into the bundle, so there is nostyles.css), and seeds aDEFAULT_SETTINGS-shapeddata.json.scripts/start-obsidian-e2e-instance.mjs— starts/reuses a private-HOMEObsidian instance bound to the worktree vault, disables Restricted Mode, and confirms PodNotes loaded viaobsidian eval.scripts/obsidian-e2e-cli.mjs— theobsidian:e2ewrapper: provision + start + run a command withvault=<worktree vault>and the privateHOMEalready applied.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 theexport HOME=$PODNOTES_E2E_OBSIDIAN_HOMEremap needed to pointtests/e2eat the isolated instance.vitest.config.ts.lintandformat:check(Biome) widened to coverscripts/.Why isolation holds
Vaults are always worktree-local under
.obsidian-e2e-vaults/podnotes-<basename>; every CLI call pinsHOMEto a per-instance private profile, and theobsidianbinary 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 shareddevvault 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
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(notdev)Boolean(app.plugins.plugins.podnotes)→true;manifest.version→2.16.0(our bundle loaded)app.vault.adapter.basePath→ the worktree-local.obsidian-e2e-vaults/...pathdevvault symlinks and the real~/Library/.../obsidian.jsonwere 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_SETTINGSdrift guard added,--register-viaremoved).