From cf433e3a73415be87e688314984ef18484328d7c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Tue, 18 Aug 2026 14:26:16 -0400 Subject: [PATCH] fix(cli): vault import with --vault no longer demands a default profile `vault import` resolved the active fingerprint unconditionally, even though an explicit --vault target never uses it. On a machine with no ~/.seedpass that made the command fail with "no profile selected" -- and it is why the ts-parity workflow has been red for 33 straight runs while every local run passed: the developer machine has a real ~/.seedpass, so the environment coupling was invisible exactly where it mattered. The profile lookup is now lazy, used only for the default-vault path. The deeper fix is in the test harness: cli.test.ts now sets a hermetic SEEDPASS_APP_DIR, so any code path that touches the default profile directory fails locally the way it fails in CI instead of borrowing the developer's real vault state. Verified as a negative control: with the hermetic env and the old code, the import test reproduces CI's exact "Unexpected end of JSON input" locally; with the fix, all 158 CLI tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8 --- js/packages/cli/src/program.ts | 9 +++++++-- js/packages/cli/test/cli.test.ts | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/js/packages/cli/src/program.ts b/js/packages/cli/src/program.ts index 587084e5..fe4059cc 100644 --- a/js/packages/cli/src/program.ts +++ b/js/packages/cli/src/program.ts @@ -1578,8 +1578,13 @@ export function buildProgram(io: ProgramIo = defaultIo): Command { } const targetMnemonic = mnemonic ?? (await resolveMnemonic(app, opts)); - const fp = await currentFingerprint(app, opts); - const vaultPath = opts.vault ?? join(app.profileDir(fp), INDEX_FILENAME); + // Resolve the profile only when the target actually depends on it: an + // explicit --vault path needs no profile, and demanding one anyway made + // `vault import --vault x` fail on machines with no ~/.seedpass — an + // environment coupling that local runs masked for months because this + // machine has one. (The CI-only parity failure was exactly this.) + const vaultPath = + opts.vault ?? join(app.profileDir(await currentFingerprint(app, opts)), INDEX_FILENAME); if (existsSync(vaultPath) && !o.yes) { const current = await openVault(vaultPath, targetMnemonic); if (Object.keys(current.index.entries).length > 0) { diff --git a/js/packages/cli/test/cli.test.ts b/js/packages/cli/test/cli.test.ts index 3bf605f4..656218c4 100644 --- a/js/packages/cli/test/cli.test.ts +++ b/js/packages/cli/test/cli.test.ts @@ -50,6 +50,12 @@ async function run(...argv: string[]): Promise { beforeAll(async () => { process.env["SEEDPASS_MNEMONIC"] = MNEMONIC; + // Hermetic app dir: without this, any code path that touches the DEFAULT + // profile directory silently depends on the developer's real ~/.seedpass — + // which is exactly how a "vault import --vault x needs a default profile" + // bug passed locally for months while failing in CI, where no ~/.seedpass + // exists. Tests must fail the way CI fails. + process.env["SEEDPASS_APP_DIR"] = await mkdtemp(join(tmpdir(), "seedpass-cli-appdir-")); const dir = await mkdtemp(join(tmpdir(), "seedpass-cli-")); vaultPath = join(dir, "vault.enc"); const key = deriveIndexKeyBytes(MNEMONIC);