Skip to content

feat(cli): refuse re-minting over a live project; never modify an existing .env#1582

Closed
shapirodaniel wants to merge 2 commits into
stack/mint-and-claim/a1/04-init-signpostfrom
stack/mint-and-claim/a1/05-remint-guardrail
Closed

feat(cli): refuse re-minting over a live project; never modify an existing .env#1582
shapirodaniel wants to merge 2 commits into
stack/mint-and-claim/a1/04-init-signpostfrom
stack/mint-and-claim/a1/05-remint-guardrail

Conversation

@shapirodaniel

@shapirodaniel shapirodaniel commented Jul 22, 2026

Copy link
Copy Markdown

Description

this PR adds claim state guardrails around pre-claimed project metadata, most importantly SANITY_AUTH_TOKEN management, to keep projects seamlessly auth'd for cli use, and later, studio / mcp server (via non-oauth'd mcp.sanity.io/mcp) uses. high level,

  • we write-once on successful sanity projects mint
  • we read and subsequently warn and/or surface directions to humans + agents when --force re-minting
  • we communicate structured errors when --json is present, and human-friendly errors when it's not
  • we leverage dotenv to avoid rolling our own env parser

Stack

(each PR reviews against its base; merge bottom-up only):

  1. stack/mint-and-claim/a1/01-terminal-presentation
  2. stack/mint-and-claim/a1/02-mint-core
  3. stack/mint-and-claim/a1/03-claim-reminders
  4. stack/mint-and-claim/a1/04-init-signpost
  5. stack/mint-and-claim/a1/05-remint-guardrail ← this diff
  6. stack/mint-and-claim/a1/06-ambient-reminder
  7. stack/mint-and-claim/a1/07-logout-env-token

What to review

The guardrail decision tree in guardExistingProject (mint.ts) — the densest logic in the stack. The .env write 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).

pr1582-force-never-modifies-env pr1582-guardrail-refusals

🤖 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 new now runs a pre-mint guardrail that reads ./.env (via new readEnvValues / 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—using lookupClaimState, the local mint ledger, and optional claim URL parsing—with structured CLIError codes and --json refusals. Server-verified expired ledger-bound projects can mint a replacement without --force; --force skips verification and still mints.

.env policy 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 optional warnings—including --json, which never writes files. Skipped append keys now show the correct values to set manually. Superseding an expired mint calls forgetMintedProject (with warnings if the ledger drop fails).

Supporting changes: getMintedProjectRecord / forgetMintedProject in claim nudges, documented append-only invariant on appendEnvValues, 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.

@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against stack/mint-and-claim/a1/04-init-signpost (36dfbdbe)

@sanity/cli

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.

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
Comment thread packages/@sanity/cli/src/commands/projects/mint.ts Outdated
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/init/initAction.ts 90.2% (+ 0.1%)
packages/@sanity/cli/src/actions/init/newCommandBanner.ts 100.0% (new)
packages/@sanity/cli/src/commands/new.ts 100.0% (new)
packages/@sanity/cli/src/commands/projects/mint.ts 99.1% (new)
packages/@sanity/cli/src/hooks/prerun/claimReminders.ts 88.9% (new)
packages/@sanity/cli/src/services/mintProject.ts 68.4% (new)
packages/@sanity/cli/src/util/claimNudges.ts 96.3% (new)
packages/@sanity/cli/src/util/envFile.ts 100.0% (new)
packages/@sanity/cli/src/util/flowOutput.ts 78.9% (new)
packages/@sanity/cli/src/util/newCommandSplash.ts 100.0% (new)
packages/@sanity/cli/src/util/terminalLink.ts 100.0% (new)

Comparing 11 changed files against main @ f6fd22ccd40bb8346e8ecdb5e0cfe54d62f54823

Overall Coverage

Metric Coverage
Statements 78.6% (- 0.0%)
Branches 70.1% (- 0.3%)
Functions 74.5% (+ 0.1%)
Lines 79.0% (- 0.0%)

@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/04-init-signpost branch from bf92b82 to e437a74 Compare July 22, 2026 16:01
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 2a8a9ef to 6fd1ab1 Compare July 22, 2026 16:01
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/util/envFile.ts Outdated
Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/04-init-signpost branch from 0d5a9d9 to 03e73c6 Compare July 22, 2026 17:03
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from a3c5b99 to 8638226 Compare July 22, 2026 17:03
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 902df26 to 55e28fe Compare July 22, 2026 17:58
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 01432a6 to 84d2fe6 Compare July 22, 2026 18:16
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 200134d to 5160e89 Compare July 22, 2026 18:23
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts Outdated
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 2f1e555 to fe566bc Compare July 22, 2026 18:33
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from b1dc8c0 to d2d61a0 Compare July 22, 2026 21:04
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/04-init-signpost branch from 5b17d16 to 09b0577 Compare July 22, 2026 21:14
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from ad4dd7b to 1e0fdca Compare July 22, 2026 21:14
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/04-init-signpost branch from dc46241 to 890f1e0 Compare July 22, 2026 21:21
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from ef9239f to 890f1e0 Compare July 22, 2026 21:21
@shapirodaniel shapirodaniel reopened this Jul 22, 2026
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from 890f1e0 to 1c27849 Compare July 22, 2026 21:29
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/04-init-signpost branch from 401aa3d to f7ea2fb Compare July 22, 2026 21:33
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from c16606b to 182305a Compare July 22, 2026 21:34
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts Outdated
…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]>
@shapirodaniel
shapirodaniel force-pushed the stack/mint-and-claim/a1/05-remint-guardrail branch from c0c3a4d to 61c9582 Compare July 22, 2026 21:56
@shapirodaniel

Copy link
Copy Markdown
Author

bugbot run

Comment thread packages/@sanity/cli/src/commands/projects/mint.ts
@shapirodaniel
shapirodaniel marked this pull request as ready for review July 22, 2026 22:47
@shapirodaniel
shapirodaniel requested a review from a team as a code owner July 22, 2026 22:47

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9fddabd. Configure here.

@jwoods02 jwoods02 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, maybe trim the comments slightly but me and my agents couldn't find any missing seams / scenarios here 👍

@shapirodaniel

Copy link
Copy Markdown
Author

all feedback addressed on #1592, canceling this PR to consolidate after offline sync

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants