Multi-agent review caught a real regression in the daemon-reuse fix:
- C1 (CRITICAL): remotePath "~" → effectivePath "." →
resolveRemotePath("." , home) = "/home/u/." but the daemon reports
filepath.Abs(".") = "/home/u" via server.info → sameRemotePath
(trailing-slash-only) = false → kill+redeploy on EVERY connect.
Same for any "..", "//" in remotePath. The client (string-join) and
daemon (filepath.Abs) lived in different path spaces.
- I1: reused.info.vaultRoot is typed string but an older/3rd-party
daemon can omit it on the wire → undefined → sameRemotePath's
.trim() threw TypeError → uncaught silent connect failure.
- I2: deploy() got the raw relative effectivePath as --vault-root →
implicit daemon-cwd dependency.
Consolidated fix:
- ConnectionManager: compute the absolute vault root ONCE
(resolveRemotePath(effectivePath, home)) and use it BOTH for the
reuse compare AND as deploy()'s remoteVaultRoot — same path space
on both sides, no cwd dependency (fixes C1 + I2 together).
- sameRemotePath: POSIX-normalise both sides (path.posix.normalize →
collapses ./../ //, then strip trailing slash); null-safe
(string|undefined); empty/blank never matches a real root, not
even empty-vs-empty (always forces the safe redeploy). Fixes C1
remainder + I1.
- haveRoot guarded `?? ''` at the call site; reused.close() catch
now logger.warn (mirrors every other close in the file) instead of
a silent swallow.
- JSDoc reworded: a spurious redeploy is SAFE but not cheap (full
pkill + upload + restart) — normalisation exists to avoid firing
it on legitimately-equal paths.
Tests: pathUtils.test.ts +6 (the "." / ~ loop case, ".."/"//"
collapse, empty-vs-empty=false, undefined-safe no-throw). The
startRpcSession reuse/mismatch glue is intentionally covered by the
now-exhaustive sameRemotePath unit suite + the connect e2e
(#351/#353) rather than a bespoke ssh/deployer mock harness —
disproportionate for thin control flow whose decision core is pure
and fully tested.
tsc clean, eslint clean, vitest 70 files / 1061 passed / 1 skipped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Field repro: changing a profile's remotePath (or deleting the old
remote vault) had NO effect — `tryReuseExistingDaemon` reused the
already-running daemon purely on socket+token presence, never
checking that its `ServerInfo.vaultRoot` still matches the profile's
requested root. The daemon kept serving its original (now wrong /
deleted) vault-root, so `fs.walk("")` returned `no such file:`, the
shadow vault populated 0 files, and every op (incl. renaming a new
note) failed `no such file`. The only workaround was to SSH in and
`pkill` the daemon by hand — exactly what should be automatic.
Fix: in `ConnectionManager.startRpcSession`, after a reuse candidate
is found, resolve the profile's `effectivePath` to absolute
(`resolveRemotePath(effectivePath, home)`) and compare it with the
reused daemon's `reused.info.vaultRoot` via the new
`sameRemotePath` (trailing-slash/whitespace tolerant, otherwise
strict — a spurious redeploy is cheap, a false match reattaches to
the wrong root). On mismatch: close the connection and fall through
to `deployer.deploy()`, whose `killExisting:true` pkills the stale
daemon and redeploys at the correct root. No manual pkill ever
needed; a remotePath change now always takes effect.
`sameRemotePath` added to util/pathUtils with 6 unit cases incl. the
exact field bug (`/home/souta/work/VaultDev` vs `/home/souta/work`
→ false → redeploy). tsc/eslint clean, vitest 70 files / 1057
passed.
Chained on fix/bootstrap-first-run-appjson so one local build has
both fixes (empty-app.json deadlock + stale-daemon root) for
end-to-end verification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security check turned up three places where the maintainer's
personal info had ended up in tracked source:
- plugin/tests/AdapterPatcher.test.ts:15
`target.greet('souta')` → `target.greet('alice')`
- plugin/tests/pathUtils.test.ts:15
`/home/souta/work/VaultDev/` → `/home/alice/vault/`
- plugin/scripts/dev-install.mjs
Default fallback for the dev vault path was hard-coded to
`../../SelfArchive-dev` (the maintainer's own dev vault folder
name). Renamed to the generic `../../dev-vault`. The
`REMOTE_SSH_DEV_VAULT` env var still wins, and the script accepts
a positional argv override too — the maintainer's actual
`SelfArchive-dev` keeps working with
`REMOTE_SSH_DEV_VAULT=/path/to/SelfArchive-dev npm run build:full`.
What stays:
- `github.com/sotashimozono/...` in every Go file's import path —
Go module canonical URL; this matches the repo URL on GitHub
and isn't replaceable.
- `work/VaultDev` mentions in README / docs / a couple of test
fixtures — generic enough not to identify the maintainer
(anyone could name a folder that), kept as-is.
No private keys / tokens / IPs / emails / hostnames were in the
tree to begin with — those live only in the user's local
`data.json` and ssh agent, not in the repo.
Bumps to 0.3.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the entire Obsidian plugin tree under plugin/ and add an empty
server/ tree (Go) and proto/ directory so the upcoming
obsidian-remote-server daemon has a home in the same repo. The plugin
remains the only thing that builds today; the server is a hello-world
skeleton that prints "not implemented yet" and exits non-zero, with a
Makefile that knows how to cross-compile for linux/darwin × amd64/arm64
once real code lands.
Background:
Approach A (vault-adapter monkey-patch) hit structural ceilings —
vault index frozen at startup, third-party plugins bypass DataAdapter,
empty-remote .obsidian read failures cascade through Templater/Kanban
etc. We are pivoting to the VS Code Remote-SSH model: a small Go
daemon on the remote host, JSON-RPC over a SSH-tunnelled WebSocket,
fsnotify-driven push events, attachment serving over HTTP on the same
socket. This restructure is the first mechanical step before the
protocol and transport land.
Mechanical changes:
- git mv all plugin sources, manifest, configs, scripts, and tests
into plugin/.
- plugin/scripts/dev-install.mjs walks one extra parent (`pluginRoot`
→ `repoRoot` → `..` for the dev vault) so REMOTE_SSH_DEV_VAULT
defaulting still resolves to ../SelfArchive-dev relative to the
repo, not to plugin/.
- CI: ci.yml runs lint/test/build with `working-directory: plugin`
and a server job that builds + tests the Go module. release.yml
builds out of plugin/ and uploads plugin/{main.js,manifest.json,
styles.css}.
- Dependabot: npm directory becomes /plugin; new gomod directory /server.
- Labeler: paths re-anchored to plugin/, with new plugin/server/proto
area labels.
Verification:
cd plugin && npm install && npx tsc --noEmit && npm test (76/76)
cd plugin && npm run build:install (367 KB; dev vault refreshed).
Go side waits on CI (no Go toolchain on the dev host).