feat(cli): refuse re-minting over a live project; never modify an existing .env#1582
Conversation
|
bugbot run |
📦 Bundle Stats —
|
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 2.2 KB | - |
| Internal (gzip) | 838 B | - |
| Bundled (raw) | 11.20 MB | - |
| Bundled (gzip) | 2.11 MB | - |
| Import time | 884ms | +1ms, +0.1% |
bin:sanity
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.90 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.14s | +8ms, +0.4% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against stack/mint-and-claim/a1/04-init-signpost (36dfbdbe)
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 114.1 KB | - |
| Internal (gzip) | 29.2 KB | - |
| Bundled (raw) | 21.76 MB | - |
| Bundled (gzip) | 3.46 MB | - |
| Import time | 785ms | -1ms, -0.1% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-build
Compared against stack/mint-and-claim/a1/04-init-signpost (36dfbdbe)
@sanity/cli-build/_internal/build
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 113.8 KB | - |
| Internal (gzip) | 28.7 KB | - |
| Bundled (raw) | 17.76 MB | - |
| Bundled (gzip) | 3.56 MB | - |
| Import time | 1.14s | +5ms, +0.4% |
@sanity/cli-build/_internal/env
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 1.8 KB | - |
| Internal (gzip) | 644 B | - |
| Bundled (raw) | 1.31 MB | - |
| Bundled (gzip) | 333.8 KB | - |
| Import time | 124ms | -3ms, -2.4% |
@sanity/cli-build/_internal/extract
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 8.6 KB | - |
| Internal (gzip) | 2.7 KB | - |
| Bundled (raw) | 155.0 KB | - |
| Bundled (gzip) | 39.5 KB | - |
| Import time | 247ms | +2ms, +0.9% |
🗺️ ./_internal/env · ./_internal/extract · @sanity/cli-build:./_internal/build treemap too large to embed · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against stack/mint-and-claim/a1/04-init-signpost (36dfbdbe)
| Metric | Value | vs stack/mint-and-claim/a1/04-init-signpost (36dfbdb) |
|---|---|---|
| Internal (raw) | 908 B | - |
| Internal (gzip) | 483 B | - |
| Bundled (raw) | 931 B | - |
| Bundled (gzip) | 491 B | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
Coverage Delta
Comparing 11 changed files against main @ Overall Coverage
|
bf92b82 to
e437a74
Compare
2a8a9ef to
6fd1ab1
Compare
|
bugbot run |
0d5a9d9 to
03e73c6
Compare
a3c5b99 to
8638226
Compare
|
bugbot run |
902df26 to
55e28fe
Compare
|
bugbot run |
01432a6 to
84d2fe6
Compare
|
bugbot run |
200134d to
5160e89
Compare
|
bugbot run |
2f1e555 to
fe566bc
Compare
|
bugbot run |
b1dc8c0 to
d2d61a0
Compare
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d2d61a0. Configure here.
5b17d16 to
09b0577
Compare
ad4dd7b to
1e0fdca
Compare
dc46241 to
890f1e0
Compare
ef9239f to
890f1e0
Compare
890f1e0 to
1c27849
Compare
401aa3d to
f7ea2fb
Compare
c16606b to
182305a
Compare
|
bugbot run |
…sting .env Claim-state-aware guardrail for `sanity new` / `sanity projects mint`: minting only proceeds when the directory's .env has no Sanity keys, when they belong to a server-verified-expired project on a ledger-bound claim token (the agent/low-code recovery path — a failed lookup or unbound evidence refuses instead), or under --force. The append into a keyless directory stays the feature's only .env write: proceed lanes leave the file byte-identical and print the new values with replace-them-yourself instructions (--json carries them in the payload plus a warning). Nothing is destructive, so --force never prompts — it only spends a mint. readEnvValues delegates parsing to dotenv, the same grammar the runtime's env injection uses. Co-Authored-By: Claude Fable 5 <[email protected]>
c0c3a4d to
61c9582
Compare
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9fddabd. Configure here.
| const foundKeys = GUARDED_ENV_KEYS.filter((key) => existing[key] !== undefined) | ||
| if (foundKeys.length === 0) return {hasExistingKeys: false} | ||
| if (this.flags.force) { | ||
| return {hasExistingKeys: true} |
There was a problem hiding this comment.
Force retains expired remint records
Medium Severity
--force returns before claim lookup, so expiredProjectId is never set and forgetMintedProject never runs. After a forced remint over an expired project, .env still points at the old id and the ledger record remains, so the next non-force run auto-proceeds and spends another mint against the rate limit—the loop the expired-lane forget was added to stop.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9fddabd. Configure here.
jwoods02
left a comment
There was a problem hiding this comment.
Nice work, maybe trim the comments slightly but me and my agents couldn't find any missing seams / scenarios here 👍
|
all feedback addressed on #1592, canceling this PR to consolidate after offline sync |


Description
this PR adds claim state guardrails around pre-claimed project metadata, most importantly
SANITY_AUTH_TOKENmanagement, to keep projects seamlessly auth'd for cli use, and later, studio / mcp server (via non-oauth'dmcp.sanity.io/mcp) uses. high level,sanity projects mint--forcere-minting--jsonis present, and human-friendly errors when it's notdotenvto avoid rolling our own env parserStack
(each PR reviews against its base; merge bottom-up only):
stack/mint-and-claim/a1/01-terminal-presentationstack/mint-and-claim/a1/02-mint-corestack/mint-and-claim/a1/03-claim-remindersstack/mint-and-claim/a1/04-init-signpoststack/mint-and-claim/a1/05-remint-guardrail← this diffstack/mint-and-claim/a1/06-ambient-reminderstack/mint-and-claim/a1/07-logout-env-tokenWhat to review
The guardrail decision tree in
guardExistingProject(mint.ts) — the densest logic in the stack. The.envwrite path is unchanged from PR2's append-only writer, now stated as an invariant: existing lines are never edited.Testing
Layer-scoped unit tests included (test:source ≥ 1:1); every layer validated standalone (full typecheck + targeted tests at each checkout); the append-only writer property-fuzzed (5,000 cases: prior file content is always a byte-prefix of the result).
🤖 Generated with Claude Code
Note
Medium Risk
Touches credential handling, provisioning rate limits, and a complex guard decision tree; mistakes could block mints or allow unwanted re-mints, but behavior is heavily tested and failures are mostly conservative refusals rather than silent overwrites.
Overview
sanity projects mint/sanity newnow runs a pre-mint guardrail that reads./.env(via newreadEnvValues/dotenv) before prompts or API calls. It blocks accidental re-mints when guarded keys (SANITY_AUTH_TOKEN,SANITY_PROJECT_ID,SANITY_CLAIM_URL) indicate a live unclaimed, already claimed, or unverifiable setup—usinglookupClaimState, the local mint ledger, and optional claim URL parsing—with structuredCLIErrorcodes and--jsonrefusals. Server-verified expired ledger-bound projects can mint a replacement without--force;--forceskips verification and still mints..envpolicy is tightened: existing lines are never edited. Fresh dirs still append-only write; any dir that already has guarded values gets new credentials printed or in the JSON payload plus optionalwarnings—including--json, which never writes files. Skipped append keys now show the correct values to set manually. Superseding an expired mint callsforgetMintedProject(with warnings if the ledger drop fails).Supporting changes:
getMintedProjectRecord/forgetMintedProjectin claim nudges, documented append-only invariant onappendEnvValues, and broad unit tests for the guard decision tree.Reviewed by Cursor Bugbot for commit 9fddabd. Bugbot is set up for automated code reviews on this repo. Configure here.