From a33aada8f57259f5d90ba1aba135df850862a20d Mon Sep 17 00:00:00 2001 From: Daniil Koryto Date: Tue, 23 Jun 2026 15:02:10 +0300 Subject: [PATCH] fix: clarify Desktop key rotation flow --- CHANGELOG.md | 3 +++ README.md | 10 ++++++++++ docs/how-it-works.md | 5 ++++- docs/troubleshooting.md | 17 +++++++++++++++++ src/cli/render.ts | 3 +++ test/cli.test.ts | 12 ++++++++++-- test/docs-contract.test.ts | 3 +++ 7 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6dd1c1..4dc8321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,9 @@ - renamed the managed install-state durability timestamp to `lastDurableSetupAt` while keeping legacy `lastSuccessfulSetupAt` readable as a backward-compatible migration path +- clarified the safe API key rotation flow in CLI success output and docs, + including the OpenCode Desktop restart needed after refreshing the managed + secret file ### Bug Fixes diff --git a/README.md b/README.md index a8b91ba..251648d 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,16 @@ That rerun flow refreshes GonkaGate-managed config, secret storage, and install-state metadata. It also normalizes only installer-owned GonkaGate activation in the old target instead of deleting unrelated OpenCode settings. +To replace the GonkaGate API key later, rerun setup with a safe secret input: + +```bash +printf '%s' "$GONKAGATE_API_KEY" | npx @gonkagate/opencode-setup --api-key-stdin --scope project --yes +``` + +If you use OpenCode Desktop, restart the desktop app after rerunning setup so +its sidecar reloads the managed secret file. The Desktop custom-provider form +does not replace the installer-managed `~/.gonkagate/opencode/api-key` file. + For `project` scope: - user-level config still owns the provider definition and secret binding diff --git a/docs/how-it-works.md b/docs/how-it-works.md index db9f539..94253e0 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -92,7 +92,10 @@ Current validated picker entries: `provider.gonkagate.options.apiKey` overrides are always blocked in v1 because current upstream docs do not clearly prove equivalent inline `{file:...}` substitution parity for this secret-binding contract. -15. Tell the user to run plain `opencode`. +15. Tell the user to run plain `opencode`, and show the safe rerun command for + replacing the managed API key later. OpenCode Desktop users should restart + the desktop app after rerunning setup so its sidecar reloads + `~/.gonkagate/opencode/api-key`. ## Why User-Level Provider Ownership diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index a7bb865..e8f23f9 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -65,6 +65,23 @@ The safe options are: - `GONKAGATE_API_KEY` - `--api-key-stdin` +## Why does OpenCode Desktop still say "Invalid credentials" after I pasted a new key? + +If setup was installed by `@gonkagate/opencode-setup`, GonkaGate auth is owned +by the managed secret file at `~/.gonkagate/opencode/api-key`. The OpenCode +config points at that file with +`provider.gonkagate.options.apiKey = {file:~/.gonkagate/opencode/api-key}`. + +The OpenCode Desktop custom-provider form can show a new key while the running +sidecar still resolves the installer-managed file. To replace the key, rerun +setup instead of relying on the Desktop form: + +```bash +printf '%s' "$GONKAGATE_API_KEY" | npx @gonkagate/opencode-setup --api-key-stdin --scope project --yes +``` + +Then restart OpenCode Desktop so it reloads the managed secret file. + ## Why does non-interactive setup require `--scope` or `--yes`? Because the installer needs an explicit safe way to choose between `user` and diff --git a/src/cli/render.ts b/src/cli/render.ts index 3ae1c04..0eb8892 100644 --- a/src/cli/render.ts +++ b/src/cli/render.ts @@ -1,4 +1,5 @@ import { CommanderError } from "commander"; +import { CONTRACT_METADATA } from "../constants/contract.js"; import type { InstallFlowResult } from "../install/contracts/install-flow.js"; import { redactSecretBearingText } from "../install/redact.js"; import type { @@ -96,6 +97,8 @@ function renderHumanResult(result: InstallFlowResult): string { `Model: ${result.modelDisplayName} (${result.modelRef})`, `Scope: ${formatScopeLabel(result.scope)}`, "Next: opencode", + `Rotate key later: printf '%s' "$GONKAGATE_API_KEY" | ${CONTRACT_METADATA.publicEntrypoint} --api-key-stdin --scope ${result.scope} --yes`, + "OpenCode Desktop: restart after rerunning setup so it reloads the managed key file.", "", ].join("\n"); } diff --git a/test/cli.test.ts b/test/cli.test.ts index 3ab07dd..2c1515c 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -477,7 +477,7 @@ test("CLI emits structured JSON failed payloads for resolved-config mismatches", } }); -test("human-readable success output ends with the minimal next step", async () => { +test("human-readable success output includes next step and key rotation guidance", async () => { const fixture = await createCliFixture(); try { @@ -492,7 +492,15 @@ test("human-readable success output ends with the minimal next step", async () = fixture.stdout.contents, /GonkaGate is configured for OpenCode\./, ); - assert.match(fixture.stdout.contents, /Next: opencode\n$/); + assert.match(fixture.stdout.contents, /Next: opencode/); + assert.match( + fixture.stdout.contents, + /Rotate key later: printf '%s' "\$GONKAGATE_API_KEY" \| npx @gonkagate\/opencode-setup --api-key-stdin --scope project --yes/, + ); + assert.match( + fixture.stdout.contents, + /OpenCode Desktop: restart after rerunning setup so it reloads the managed key file\.\n$/, + ); } finally { await fixture.harness.cleanup(); } diff --git a/test/docs-contract.test.ts b/test/docs-contract.test.ts index 2ba2755..2961327 100644 --- a/test/docs-contract.test.ts +++ b/test/docs-contract.test.ts @@ -52,6 +52,7 @@ test("README captures the shipped runtime truth and current opencode contract", /WSL/i, /--api-key-stdin/, /GONKAGATE_API_KEY/, + /OpenCode Desktop[\s\S]*restart[\s\S]*managed secret file/i, /~\/\.gonkagate\/opencode\/backups\/project-config/, ]); @@ -139,6 +140,7 @@ test("implementation docs capture the shipped setup architecture and boundaries" /WSL/i, /inherited user-profile ACLs|does not attempt to rewrite Windows ACLs/i, /project scope writes only activation settings/i, + /OpenCode Desktop[\s\S]*restart[\s\S]*~\/\.gonkagate\/opencode\/api-key/i, /~\/\.gonkagate\/opencode\/backups\/project-config/, ]); @@ -164,6 +166,7 @@ test("implementation docs capture the shipped setup architecture and boundaries" /provider options|model options|headers|compatibility metadata/i, /provider\.gonkagate\.models[\s\S]*\/models|\/models[\s\S]*provider\.gonkagate\.models/i, /provider\.gonkagate\.options\.apiKey/i, + /OpenCode Desktop[\s\S]*Invalid credentials[\s\S]*--api-key-stdin/i, /raw `opencode debug config` output/i, ]);