Skip to content

fix: shell-independent API key resolution (#11)#13

Merged
jrenaldi79 merged 6 commits intomainfrom
fix/issue-11-shell-independent-keys
Mar 15, 2026
Merged

fix: shell-independent API key resolution (#11)#13
jrenaldi79 merged 6 commits intomainfrom
fix/issue-11-shell-independent-keys

Conversation

@jrenaldi79
Copy link
Owner

@jrenaldi79 jrenaldi79 commented Mar 14, 2026

Summary

  • Adds src/utils/env-loader.js that loads API keys from sidecar's .env and OpenCode's auth.json into process.env at CLI bootstrap, before validation runs
  • Simplifies validateApiKey() in validators.js to a pure process.env check with actionable error message
  • Replaces scattered dotenv + legacy migration logic in bin/sidecar.js with a single loadCredentials() call
  • Excludes .worktrees/ from Jest test discovery to prevent duplicate test runs

Priority order: process.env > ~/.config/sidecar/.env > ~/.local/share/opencode/auth.json (first wins, never overwrite)

Closes #11

Test plan

  • 11 new unit + integration tests in tests/env-loader.test.js
  • Full test suite passes (86 suites, 1638 tests)
  • Lint clean on changed files
  • Manual smoke test: node bin/sidecar.js --version
  • Verify fix on a machine with keys only in ~/.zshrc (not ~/.zshenv)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Centralized credential loading from multiple sources with deterministic precedence and legacy-key migration support.
    • Improved startup behavior for resolving API keys.
  • Bug Fixes / Validation

    • Stricter validation for missing credentials with clearer, actionable error messages.
  • Documentation

    • Added design doc and zsh-specific setup/troubleshooting guidance for API key configuration.
  • Tests

    • Added comprehensive tests covering credential loading, precedence, migration, and idempotence.

jrenaldi79 and others added 5 commits March 14, 2026 16:17
Fixes the 'Missing Authentication header' bug in non-interactive shells
by loading sidecar's .env and auth.json into process.env at startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New module loads API keys from sidecar .env and auth.json into
process.env at CLI bootstrap, with priority: env > .env > auth.json.
Exports loadEnvEntries from api-key-store for reuse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace dotenv + legacy migration in CLI with loadCredentials().
Simplify validateApiKey() to pure process.env check with actionable
error message mentioning sidecar setup, ~/.zshenv, and auth.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add zsh-specific note to Option B setup and troubleshooting entry
for "Missing Authentication header" in non-interactive shells.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents Jest from running duplicate tests from git worktree
directories. Applied to jest.config.js and all package.json
test scripts that override testPathIgnorePatterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

The PR adds a centralized credential loader (src/utils/env-loader.js) that aggregates API keys from process.env, a sidecar .env, and auth.json in deterministic priority, updates startup to invoke it, exposes loadEnvEntries from api-key-store, tightens validator checks, adds tests, and updates docs and test ignore patterns.

Changes

Cohort / File(s) Summary
Credential Loader
src/utils/env-loader.js
New module exporting loadCredentials() that loads credentials from process.env, sidecar .env, and auth.json in priority order, performs legacy key migration, and merges without overwriting existing env vars.
Startup / Init
bin/sidecar.js
Replaced dotenv/inline migration logic with a centralized loadCredentials() invocation at startup, changing initialization order and source precedence.
API Key Store
src/utils/api-key-store.js
Public API changed: removed saveApiKey() export and added exported loadEnvEntries() (other reads unchanged).
Validation
src/utils/validators.js
Removed auth.json bypass in validateApiKey(); validators now require env vars and show updated remediation error text referencing sidecar setup, zshenv, and auth.json.
Tests
tests/env-loader.test.js
New comprehensive test suite validating load order, priority, legacy migration, idempotence, missing-file behavior, and validator integration.
Docs & Specs
docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md, CLAUDE.md, skill/SKILL.md
Added design doc and updated module/API listings and troubleshooting (zsh guidance, missing auth header troubleshooting).
Test Config / Scripts
jest.config.js, package.json
Added \\.worktrees/ to Jest ignore patterns and updated test scripts to ignore worktrees in integration/all test runs.

Sequence Diagram

sequenceDiagram
    participant CLI as Sidecar CLI
    participant Loader as env-loader
    participant SidecarEnv as Sidecar .env
    participant AuthJSON as auth.json
    participant ProcessEnv as process.env
    participant Validator as validateApiKey()

    CLI->>Loader: loadCredentials()
    Loader->>ProcessEnv: Inspect existing env keys
    ProcessEnv-->>Loader: Existing values (if any)
    Loader->>SidecarEnv: Read ~/.config/sidecar/.env
    SidecarEnv-->>Loader: .env entries
    Loader->>ProcessEnv: Merge .env entries (no overwrite)
    Loader->>Loader: Migrate legacy key names (e.g., GEMINI_API_KEY → GOOGLE_GENERATIVE_AI_API_KEY)
    Loader->>AuthJSON: Read ~/.local/share/opencode/auth.json
    AuthJSON-->>Loader: JSON credentials
    Loader->>ProcessEnv: Merge auth.json entries (if not set)
    Loader-->>CLI: Done
    CLI->>Validator: validateApiKey()
    Validator->>ProcessEnv: Check required API env var
    ProcessEnv-->>Validator: Key present or undefined
    Validator-->>CLI: Validation result / error message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through dots and JSON trees,
Bringing keys from shell and sidecar breeze.
I never stomp on values already there,
I move legacy names with gentle care.
Now non-interactive shells can clearly share! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: shell-independent API key resolution' directly and clearly describes the main objective: enabling API key resolution independent of shell configuration sourcing, addressing issue #11.
Linked Issues check ✅ Passed The PR comprehensively implements a fallback mechanism to load credentials from standard locations (sidecar .env and auth.json) at CLI bootstrap, resolving the root cause of issue #11 where keys in ~/.zshrc were unavailable in non-interactive shells.
Out of Scope Changes check ✅ Passed All code changes directly support the shell-independent credential resolution objective: env-loader.js implements credential loading, validators.js updated for process.env checks, bin/sidecar.js integrates env-loader, and jest.config.js/package.json changes address test infrastructure for .worktrees isolation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-11-shell-independent-keys
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
skill/SKILL.md (1)

876-879: Consider documenting credential source precedence explicitly in troubleshooting.

Adding a short note like “resolution order is process.env~/.config/sidecar/.env~/.local/share/opencode/auth.json (first wins)” would reduce ambiguity when multiple sources are present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skill/SKILL.md` around lines 876 - 879, Add a single-sentence troubleshooting
note in SKILL.md near the API key setup section that explicitly states the
credential source precedence: "resolution order is process.env →
~/.config/sidecar/.env → ~/.local/share/opencode/auth.json (first wins)"; place
it alongside the existing three bullet fixes so readers know which source takes
priority when multiple credentials exist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md`:
- Line 73: Update the spec's unit test path reference: replace the incorrect
path string "tests/utils/env-loader.test.js" with the actual test file path
"tests/env-loader.test.js" in the document (the line starting with "Unit Tests:
`tests/utils/env-loader.test.js`") so the spec correctly points to the added
test file.
- Around line 19-23: Add a language/info string "text" to the fenced code blocks
that show the env precedence list (the block starting with "1. process.env
(already set)") and the block starting "Error: GOOGLE_GENERATIVE_AI_API_KEY not
found." (and the other occurrence around lines 50-58) so the fences read ```text
instead of ```, satisfying MD040; locate these by searching for those exact
block contents and update each opening fence only.

In `@src/utils/env-loader.js`:
- Around line 8-10: Convert the repository to ESM so env-loader.js can be
migrated: add "type": "module" to package.json, update logger.js,
api-key-store.js, and auth-json.js to export via ESM (export default / named
exports) and adjust their internal require/module.exports usage, then change
env-loader.js to use import statements for logger, loadEnvEntries,
PROVIDER_ENV_MAP, LEGACY_KEY_NAMES, and readAuthJsonKeys; ensure exported symbol
names match (or add named exports) so env-loader.js can import them directly.

In `@src/utils/validators.js`:
- Around line 252-257: The remediation text that builds the error string for
providerInfo.key currently hardcodes a single auth.json location; update that
message to avoid implying a single path by referring to the "default auth.json
path" or making it explicit that the shown path is an example (e.g. "or add key
to the default auth.json path (e.g. ~/.local/share/opencode/auth.json)"). Modify
the error string construction around the code that references providerInfo.key
so the third bullet becomes generic wording like "or add key to the default
auth.json path (for example: ~/.local/share/opencode/auth.json)", preserving
providerInfo.key interpolation and the other bullets.

---

Nitpick comments:
In `@skill/SKILL.md`:
- Around line 876-879: Add a single-sentence troubleshooting note in SKILL.md
near the API key setup section that explicitly states the credential source
precedence: "resolution order is process.env → ~/.config/sidecar/.env →
~/.local/share/opencode/auth.json (first wins)"; place it alongside the existing
three bullet fixes so readers know which source takes priority when multiple
credentials exist.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a350fb4-dfc3-4612-b4da-b7b5d00cb920

📥 Commits

Reviewing files that changed from the base of the PR and between dbe2ba3 and 3dc379f.

📒 Files selected for processing (10)
  • CLAUDE.md
  • bin/sidecar.js
  • docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md
  • jest.config.js
  • package.json
  • skill/SKILL.md
  • src/utils/api-key-store.js
  • src/utils/env-loader.js
  • src/utils/validators.js
  • tests/env-loader.test.js

Comment on lines +19 to +23
```
1. process.env (already set) <- highest, never overwritten
2. ~/.config/sidecar/.env <- user-configured via `sidecar setup`
3. ~/.local/share/opencode/auth.json <- OpenCode SDK fallback
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.

Both fenced blocks should include a language/info string to satisfy MD040.

Proposed fix
-```
+```text
 1. process.env (already set)     <- highest, never overwritten
 2. ~/.config/sidecar/.env        <- user-configured via `sidecar setup`
 3. ~/.local/share/opencode/auth.json  <- OpenCode SDK fallback

@@
- +text
Error: GOOGLE_GENERATIVE_AI_API_KEY not found.
@@

  • Add key to ~/.local/share/opencode/auth.json

Also applies to: 50-58

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md` around
lines 19 - 23, Add a language/info string "text" to the fenced code blocks that
show the env precedence list (the block starting with "1. process.env (already
set)") and the block starting "Error: GOOGLE_GENERATIVE_AI_API_KEY not found."
(and the other occurrence around lines 50-58) so the fences read ```text instead
of ```, satisfying MD040; locate these by searching for those exact block
contents and update each opening fence only.

Comment on lines +8 to +10
const { logger } = require('./logger');
const { loadEnvEntries, PROVIDER_ENV_MAP, LEGACY_KEY_NAMES } = require('./api-key-store');
const { readAuthJsonKeys } = require('./auth-json');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "package.json module type:"
jq -r '.type // "<missing>"' package.json

echo
echo "CommonJS usage in changed src/bin files:"
rg -nP --type=js '\b(require\s*\(|module\.exports\b)' src/utils/env-loader.js bin/sidecar.js

Repository: jrenaldi79/sidecar

Length of output: 1582


🏁 Script executed:

# Check the three imported modules
echo "=== logger.js (first 15 lines) ==="
head -15 src/utils/logger.js

echo
echo "=== api-key-store.js (first 15 lines) ==="
head -15 src/utils/api-key-store.js

echo
echo "=== auth-json.js (first 15 lines) ==="
head -15 src/utils/auth-json.js

echo
echo "=== Check for module.exports in dependencies ==="
rg -n 'module\.exports|export ' src/utils/{logger,api-key-store,auth-json}.js

echo
echo "=== Check if CLAUDE.md was updated ==="
rg 'env-loader|env_loader' CLAUDE.md || echo "No mention of env-loader in CLAUDE.md"

Repository: jrenaldi79/sidecar

Length of output: 1998


Convert to ESM as part of broader package migration.

This file uses CommonJS (require/module.exports) in a src/ directory, violating the ESM-only guideline. However, converting only this file is not feasible—all three imported modules (logger, api-key-store, auth-json) are currently CommonJS, and package.json lacks "type": "module".

To complete the conversion:

  • Set "type": "module" in package.json
  • Convert logger.js, api-key-store.js, and auth-json.js to ESM exports
  • Update env-loader.js to use ESM imports (as proposed in original comment)

CLAUDE.md was correctly updated with env-loader.js documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/env-loader.js` around lines 8 - 10, Convert the repository to ESM
so env-loader.js can be migrated: add "type": "module" to package.json, update
logger.js, api-key-store.js, and auth-json.js to export via ESM (export default
/ named exports) and adjust their internal require/module.exports usage, then
change env-loader.js to use import statements for logger, loadEnvEntries,
PROVIDER_ENV_MAP, LEGACY_KEY_NAMES, and readAuthJsonKeys; ensure exported symbol
names match (or add named exports) so env-loader.js can import them directly.

Comment on lines +252 to +257
error: `Error: ${providerInfo.key} not found.\n\n` +
'In non-interactive shells (Claude Code, CI), ~/.zshrc is not sourced.\n' +
'Fix with one of:\n' +
' - Run `sidecar setup` to store keys in sidecar\'s config\n' +
' - Move your export to ~/.zshenv (sourced by all zsh shells)\n' +
' - Add key to ~/.local/share/opencode/auth.json'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid hardcoding a single auth.json path in the remediation text.

The message currently implies only one location. In non-default setups this can misdirect users; make it explicitly “default path” or generic.

Suggested wording tweak
-        '  - Add key to ~/.local/share/opencode/auth.json'
+        '  - Add key to your OpenCode auth.json (default: ~/.local/share/opencode/auth.json)'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
error: `Error: ${providerInfo.key} not found.\n\n` +
'In non-interactive shells (Claude Code, CI), ~/.zshrc is not sourced.\n' +
'Fix with one of:\n' +
' - Run `sidecar setup` to store keys in sidecar\'s config\n' +
' - Move your export to ~/.zshenv (sourced by all zsh shells)\n' +
' - Add key to ~/.local/share/opencode/auth.json'
error: `Error: ${providerInfo.key} not found.\n\n` +
'In non-interactive shells (Claude Code, CI), ~/.zshrc is not sourced.\n' +
'Fix with one of:\n' +
' - Run `sidecar setup` to store keys in sidecar\'s config\n' +
' - Move your export to ~/.zshenv (sourced by all zsh shells)\n' +
' - Add key to your OpenCode auth.json (default: ~/.local/share/opencode/auth.json)'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/validators.js` around lines 252 - 257, The remediation text that
builds the error string for providerInfo.key currently hardcodes a single
auth.json location; update that message to avoid implying a single path by
referring to the "default auth.json path" or making it explicit that the shown
path is an example (e.g. "or add key to the default auth.json path (e.g.
~/.local/share/opencode/auth.json)"). Modify the error string construction
around the code that references providerInfo.key so the third bullet becomes
generic wording like "or add key to the default auth.json path (for example:
~/.local/share/opencode/auth.json)", preserving providerInfo.key interpolation
and the other bullets.

Address CodeRabbit review feedback: correct test file path in spec,
add credential resolution order to troubleshooting entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md (1)

19-23: ⚠️ Potential issue | 🟡 Minor

Add language tags to fenced code blocks to satisfy MD040.

Both opening fences should include an info string (e.g., text).

Proposed fix
-```
+```text
1. process.env (already set)     <- highest, never overwritten
2. ~/.config/sidecar/.env        <- user-configured via `sidecar setup`
3. ~/.local/share/opencode/auth.json  <- OpenCode SDK fallback
-```
+```text
Error: GOOGLE_GENERATIVE_AI_API_KEY not found.

In non-interactive shells (Claude Code, CI), ~/.zshrc is not sourced.
Fix with one of:
  - Run `sidecar setup` to store keys in sidecar's config
  - Move your export to ~/.zshenv (sourced by all zsh shells)
  - Add key to ~/.local/share/opencode/auth.json
</details>


Also applies to: 50-58

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md around
lines 19 - 23, The Markdown fenced code blocks in the file lack info strings
(violating MD040); update both opening fences around the list and the error
message blocks (and the other block at lines ~50-58) to include an info string
such as text (e.g., change totext) so each fenced code block has a
language tag and satisfies MD040.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md:

  • Around line 19-23: The Markdown fenced code blocks in the file lack info
    strings (violating MD040); update both opening fences around the list and the
    error message blocks (and the other block at lines ~50-58) to include an info
    string such as text (e.g., change totext) so each fenced code block has
    a language tag and satisfies MD040.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `ea6a05a3-023b-405b-9010-aea24624f8db`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3dc379f72856229c0df722fa563466791a70fd22 and 901f40b35dc695c9ea7887573fa781c0a7ec4e23.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `docs/superpowers/specs/2026-03-14-shell-independent-keys-design.md`
* `skill/SKILL.md`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@jrenaldi79 jrenaldi79 merged commit ddee8e0 into main Mar 15, 2026
2 checks passed
@jrenaldi79 jrenaldi79 deleted the fix/issue-11-shell-independent-keys branch March 15, 2026 01:47
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.

Sidecar fails with 'Missing Authentication header' when API keys are in ~/.zshrc

1 participant