RCA-2026-03-27-002: Claude Code Credential Vault Architecture

Executive Summary

Claude Code sessions failed with "Invalid API key" after implementing vault-based credential isolation. Root cause: symlink pointed to wrong file (credentials.json instead of .credentials.json). Claude Code stores OAuth tokens in .credentials.json (dot-prefixed hidden file), not the settings file. Resolution: create symlink for .credentials.json to vault, enabling proper credential isolation with gcvault mount/unmount cycle.

Timeline

Time Event

2026-03-25 19:17

Initial symlink created: ~/.claude/credentials.json → vault/credentials.json

2026-03-27 ~09:00

Discovered Claude still worked with vault unmounted (unexpected)

2026-03-27 09:02

Identified .credentials.json (local file) as auth token source

2026-03-27 09:03

Deleted .credentials.json to enforce vault isolation

2026-03-27 09:04

New Claude sessions fail: "Invalid API key"

2026-03-27 09:04

Re-login via /login in working session

2026-03-27 09:05

Created correct symlink: ~/.claude/.credentials.json → vault/.credentials.json

2026-03-27 09:06

Verified mount/unmount cycle works correctly

Problem Statement

Symptoms

  • Claude Code worked when credentials vault was unmounted (defeating purpose of vault isolation)

  • After deleting local credential file, Claude failed with "Invalid API key"

  • /login command created new local file instead of using symlinked vault file

Expected Behavior

  • Claude Code should fail when credentials vault is unmounted

  • Claude Code should work when credentials vault is mounted

  • OAuth tokens should be stored in vault, not locally

Actual Behavior

  • Claude Code used local .credentials.json fallback file

  • Symlink to credentials.json only handled settings, not auth tokens

Root Cause

5 Whys Analysis

Why # Question and Answer

1

Why did Claude work with vault unmounted?
Because: Auth token was stored in local .credentials.json, not the symlinked credentials.json.

2

Why was the token in a different file?
Because: Claude Code uses two credential files with different purposes.

3

Why wasn’t this known during initial setup?
Because: File inspection only checked credentials.json (visible), not .credentials.json (hidden).

4

Why wasn’t the hidden file discovered?
Because: ls without -a flag doesn’t show dot-prefixed files.

5

Why does Claude use two credential files?
Because: Separation of concerns: settings/metadata vs auth tokens (security pattern).

Root Cause Statement

Claude Code separates credentials into two files:

  • credentials.json - Settings, account metadata, cached configs (44KB)

  • .credentials.json - OAuth access tokens (433 bytes)

Initial vault symlink targeted the wrong file. The auth token lives in the hidden dot-prefixed file.

Claude Code Credential Architecture

File Purpose Contents

credentials.json

Settings & metadata

autoUpdates, oauthAccount (metadata only), projects, userID, cached configs

.credentials.json

OAuth authentication

Access token for Claude Max/Pro subscription

Key Discovery

The oauthAccount object in credentials.json contains:

accountUuid, displayName, emailAddress, organizationName, organizationRole...

But NOT the actual OAuth token. The token is stored separately in .credentials.json.

Resolution

Immediate Actions

# 1. Move auth token to vault
mv ~/.claude/.credentials.json ~/atelier/_vaults/mounted/credentials/claude/

# 2. Create symlink for auth file (the important one)
ln -s ~/atelier/_vaults/mounted/credentials/claude/.credentials.json ~/.claude/.credentials.json

# 3. Verify both symlinks exist
ls -la ~/.claude/ | grep cred

Expected Configuration

~/.claude/
β”œβ”€β”€ .credentials.json -> vault/.credentials.json  # AUTH TOKEN
└── credentials.json -> vault/credentials.json    # SETTINGS

Verification

# Test vault isolation
gcvault unmount credentials
claude "test" 2>&1 | head -2   # Should fail: Invalid API key

gcvault mount credentials
claude "test" 2>&1 | head -2   # Should work

Prevention

  • Always use ls -la (not ls -l) to see hidden files

  • Check for dot-prefixed variants of config files

  • Test isolation by unmounting vault and verifying failure

  • Document both the settings file AND auth file locations

find Command Lesson (CLI Mastery)

During investigation, a find syntax error was identified:

# WRONG - orphaned argument
find /path -type d "claude"

# CORRECT - use -name predicate
find /path -type d -name "claude"
find /path -name "*.json" -type f
find /path -iname "claude"  # case-insensitive

Lessons Learned

Category Lesson

Hidden files

Always check for dot-prefixed variants when working with credential/config files

Vault architecture

Test the failure case (unmount) before considering setup complete

Claude Code internals

OAuth tokens stored separately from account metadata for security isolation

find syntax

Predicates (-name, -type) require flags - bare arguments are syntax errors