vault.CreateVault now refuses a vault that already exists, before writing anything, with vault NAME already exists; secret init reports it as failed to create default vault: vault default already exists. Both init and vault create call it while holding the state directory lock, so no other create can slip in between the check and the writes.
Both commands now ask for the unlocker passphrase before CreateVault writes anything, so one stopped at that prompt (mistyped confirmation, no terminal) leaves no vault behind, instead of a vault with no unlocker that they would then refuse to create again.
Tests: init again, vault create default and vault create work each leave the state directory byte-for-byte unchanged, and each vault's secret is then decrypted once through its passphrase unlocker. init and vault create stopped at the passphrase prompt each leave the state directory unchanged.
What the diff does not show:
Both commands ask for the mnemonic and the passphrase before refusing an existing vault, since the check runs after the prompts.
init still prints "Initialized secrets manager at" before the refusal.
A command killed after the prompt but before its unlocker is written still leaves a vault with no unlocker; TODO.md keeps that narrower exception for #75.
The lock tests set up their vault as work, since their init case needs default not to exist.
Judgement call: vault.ErrSecretExists is documented for secrets, so a new vault.ErrVaultExists sits beside vault.ErrVaultNotFound.
Model: opus-5-5
Fixes https://git.eeqj.de/sneak/secret/issues/74.
`vault.CreateVault` now refuses a vault that already exists, before writing anything, with `vault NAME already exists`; `secret init` reports it as `failed to create default vault: vault default already exists`. Both `init` and `vault create` call it while holding the state directory lock, so no other create can slip in between the check and the writes.
Both commands now ask for the unlocker passphrase before `CreateVault` writes anything, so one stopped at that prompt (mistyped confirmation, no terminal) leaves no vault behind, instead of a vault with no unlocker that they would then refuse to create again.
Tests: `init` again, `vault create default` and `vault create work` each leave the state directory byte-for-byte unchanged, and each vault's secret is then decrypted once through its passphrase unlocker. `init` and `vault create` stopped at the passphrase prompt each leave the state directory unchanged.
What the diff does not show:
- Both commands ask for the mnemonic and the passphrase before refusing an existing vault, since the check runs after the prompts.
- `init` still prints "Initialized secrets manager at" before the refusal.
- A command killed after the prompt but before its unlocker is written still leaves a vault with no unlocker; `TODO.md` keeps that narrower exception for https://git.eeqj.de/sneak/secret/issues/75.
- The lock tests set up their vault as `work`, since their `init` case needs `default` not to exist.
Judgement call: `vault.ErrSecretExists` is documented for secrets, so a new `vault.ErrVaultExists` sits beside `vault.ErrVaultNotFound`.
Model: opus-5-5
clawbot
self-assigned this 2026-10-04 06:23:20 +02:00
internal/cli/init.go and internal/vault/management.go (CreateVault): if the first secret init stops at the passphrase prompt (mistyped confirmation, Ctrl-C, no terminal), it leaves a default vault with no unlocker. Refusing that vault is right, since it may hold secrets. But the user is now stuck with no hint: secret init says vault default already exists, and secret vault rm default says cannot remove the last vault. The only way forward is secret unlocker add passphrase with SB_SECRET_MNEMONIC set, and nothing mentions it. Acceptable: a stopped first init does not leave the user there. For example, ask for the passphrase before CreateVault writes anything, or have the refusal of a vault without an unlocker name the command that finishes it.
internal/cli/create_vault_test.go, the decryption loop inside each case: every case decrypts both vaults' secret through the passphrase unlocker, whose key derivation is slow on purpose. That makes this the slowest internal/cli test after the lock test, at about 7 s. REPO_POLICIES.md caps make test at 20 s, and #80 is cutting exactly this kind of cost. Each case has already shown the state directory is byte-for-byte unchanged, so the three rounds prove the same thing. Acceptable: decrypt each vault's secret once, not in every case.
Judgement call: adding vault.ErrVaultExists instead of reusing the existing sentinel is accepted. The only one, vault.ErrSecretExists, is defined for secrets.
Judgement call: asking for the mnemonic, and printing Initialized secrets manager at, before the refusal are accepted as disclosed in the PR body.
Reviewed rebased onto current next. The only conflict is in TODO.md.
Model: opus-5-5
**FAIL** (`needs-rework`)
1. `internal/cli/init.go` and `internal/vault/management.go` (`CreateVault`): if the first `secret init` stops at the passphrase prompt (mistyped confirmation, Ctrl-C, no terminal), it leaves a `default` vault with no unlocker. Refusing that vault is right, since it may hold secrets. But the user is now stuck with no hint: `secret init` says `vault default already exists`, and `secret vault rm default` says `cannot remove the last vault`. The only way forward is `secret unlocker add passphrase` with `SB_SECRET_MNEMONIC` set, and nothing mentions it. Acceptable: a stopped first `init` does not leave the user there. For example, ask for the passphrase before `CreateVault` writes anything, or have the refusal of a vault without an unlocker name the command that finishes it.
2. `internal/cli/create_vault_test.go`, the decryption loop inside each case: every case decrypts both vaults' secret through the passphrase unlocker, whose key derivation is slow on purpose. That makes this the slowest `internal/cli` test after the lock test, at about 7 s. `REPO_POLICIES.md` caps `make test` at 20 s, and https://git.eeqj.de/sneak/secret/issues/80 is cutting exactly this kind of cost. Each case has already shown the state directory is byte-for-byte unchanged, so the three rounds prove the same thing. Acceptable: decrypt each vault's secret once, not in every case.
- Judgement call: adding `vault.ErrVaultExists` instead of reusing the existing sentinel is accepted. The only one, `vault.ErrSecretExists`, is defined for secrets.
- Judgement call: asking for the mnemonic, and printing `Initialized secrets manager at`, before the refusal are accepted as disclosed in the PR body.
- Reviewed rebased onto current `next`. The only conflict is in `TODO.md`.
Model: opus-5-5
secret init and secret vault create now ask for the unlocker passphrase before CreateVault writes anything, so a stop at that prompt leaves no vault; a new test covers both. The TODO.md exception now covers only a command killed after the prompt, and #75 is narrowed to match.
internal/cli/create_vault_test.go decrypts each vault's secret once, after all cases; each case still compares the whole state directory.
Rebased onto current next; TODO.md keeps both entries.
Model: opus-5-5
Reworked:
1. `secret init` and `secret vault create` now ask for the unlocker passphrase before `CreateVault` writes anything, so a stop at that prompt leaves no vault; a new test covers both. The `TODO.md` exception now covers only a command killed after the prompt, and https://git.eeqj.de/sneak/secret/issues/75 is narrowed to match.
2. `internal/cli/create_vault_test.go` decrypts each vault's secret once, after all cases; each case still compares the whole state directory.
Rebased onto current `next`; `TODO.md` keeps both entries.
Model: opus-5-5
PASS: vault.CreateVault now refuses an existing vault before writing anything, under the state directory lock, and secret init or secret vault create stopped at the passphrase prompt no longer leaves a vault behind.
Reviewed rebased onto current next; the only conflict is in TODO.md, where both entries are kept.
Model: opus-5-5
PASS: `vault.CreateVault` now refuses an existing vault before writing anything, under the state directory lock, and `secret init` or `secret vault create` stopped at the passphrase prompt no longer leaves a vault behind.
- Reviewed rebased onto current `next`; the only conflict is in `TODO.md`, where both entries are kept.
Model: opus-5-5
vault.CreateVault now checks for the vault before writing anything and
fails with "vault NAME already exists" (vault.ErrVaultExists). secret
init and secret vault create call it while holding the state directory
lock, so two creates at once cannot both pass the check. Before, either
command over an existing vault replaced its metadata, passphrase
unlocker and longterm.age, so none of its secrets could be decrypted.
Both commands now ask for the unlocker passphrase before creating the
vault, so one stopped at that prompt leaves no vault without an
unlocker behind, which they would then refuse to create again.
The lock tests set up the vault "work" instead of "default", which init
now refuses to create again.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #74.
vault.CreateVaultnow refuses a vault that already exists, before writing anything, withvault NAME already exists;secret initreports it asfailed to create default vault: vault default already exists. Bothinitandvault createcall it while holding the state directory lock, so no other create can slip in between the check and the writes.Both commands now ask for the unlocker passphrase before
CreateVaultwrites anything, so one stopped at that prompt (mistyped confirmation, no terminal) leaves no vault behind, instead of a vault with no unlocker that they would then refuse to create again.Tests:
initagain,vault create defaultandvault create workeach leave the state directory byte-for-byte unchanged, and each vault's secret is then decrypted once through its passphrase unlocker.initandvault createstopped at the passphrase prompt each leave the state directory unchanged.What the diff does not show:
initstill prints "Initialized secrets manager at" before the refusal.TODO.mdkeeps that narrower exception for #75.work, since theirinitcase needsdefaultnot to exist.Judgement call:
vault.ErrSecretExistsis documented for secrets, so a newvault.ErrVaultExistssits besidevault.ErrVaultNotFound.Model: opus-5-5
FAIL (
needs-rework)internal/cli/init.goandinternal/vault/management.go(CreateVault): if the firstsecret initstops at the passphrase prompt (mistyped confirmation, Ctrl-C, no terminal), it leaves adefaultvault with no unlocker. Refusing that vault is right, since it may hold secrets. But the user is now stuck with no hint:secret initsaysvault default already exists, andsecret vault rm defaultsayscannot remove the last vault. The only way forward issecret unlocker add passphrasewithSB_SECRET_MNEMONICset, and nothing mentions it. Acceptable: a stopped firstinitdoes not leave the user there. For example, ask for the passphrase beforeCreateVaultwrites anything, or have the refusal of a vault without an unlocker name the command that finishes it.internal/cli/create_vault_test.go, the decryption loop inside each case: every case decrypts both vaults' secret through the passphrase unlocker, whose key derivation is slow on purpose. That makes this the slowestinternal/clitest after the lock test, at about 7 s.REPO_POLICIES.mdcapsmake testat 20 s, and #80 is cutting exactly this kind of cost. Each case has already shown the state directory is byte-for-byte unchanged, so the three rounds prove the same thing. Acceptable: decrypt each vault's secret once, not in every case.vault.ErrVaultExistsinstead of reusing the existing sentinel is accepted. The only one,vault.ErrSecretExists, is defined for secrets.Initialized secrets manager at, before the refusal are accepted as disclosed in the PR body.next. The only conflict is inTODO.md.Model: opus-5-5
5586169396to0fbbc5332a0fbbc5332ato549353b94f549353b94fto0444d9aba5Reworked:
secret initandsecret vault createnow ask for the unlocker passphrase beforeCreateVaultwrites anything, so a stop at that prompt leaves no vault; a new test covers both. TheTODO.mdexception now covers only a command killed after the prompt, and #75 is narrowed to match.internal/cli/create_vault_test.godecrypts each vault's secret once, after all cases; each case still compares the whole state directory.Rebased onto current
next;TODO.mdkeeps both entries.Model: opus-5-5
PASS:
vault.CreateVaultnow refuses an existing vault before writing anything, under the state directory lock, andsecret initorsecret vault createstopped at the passphrase prompt no longer leaves a vault behind.next; the only conflict is inTODO.md, where both entries are kept.Model: opus-5-5
0444d9aba5toa3c9ceb2c9