As developers, we love automation tools and CLI wrappers. They save us time, abstract away complex configurations, and make our workflows smoother. But what happens when the tool designed to manage your configuration lies to you?
Recently, I encountered a fascinating edge case while managing API keys for my Claude Code environment using a third-party CLI helper (@z_ai/coding-helper).
This is a story about "split-brain" configurations, digging into node_modules to find the truth, and why you should never blindly trust a CLI's success message.
The Ghostly "429 Plan Expired" Error
My setup involved using Claude Code powered by a specific API plan (GLM Coding Plan). After my initial API key expired, I renewed my subscription, got a new key, and ran the standard command provided by the CLI helper to reload my credentials:
chelper auth reload claude
I even ran the built-in health check (chelper doctor), and everything returned a perfect string of green checkmarks. Path? Valid. API Key & Network? Valid. Plan? Active.
However, the moment I tried to invoke a Model Context Protocol (MCP) tool—specifically, the web-reader tool—the agent instantly crashed with this error:
Error: MCP error -429: {"error":{"code":1309,"message":"Your GLM Coding Plan has expired, please renew to restore access."}}
Wait a minute. The health check passed, standard chats worked perfectly, but MCP tools were screaming that my account was expired.
The Investigation: Spot the Difference
Since the UI and the CLI tool were giving me conflicting information, I bypassed them and went straight to the underlying configuration files.
I checked the two places where credentials could theoretically be stored:
- The CLI helper's main config (
~/.chelper/config.yaml): This contained my brand new, valid API key. - Claude Code's MCP config (
~/.claude.json): Looking at themcpServersobject, I checked the HTTP headers.
Boom. The Authorization: Bearer header for the MCP tools was still using the old, expired API key.
We had a "split-brain" scenario. The MCP tools were reading from a stale configuration file.
Root Cause Analysis: Diving into node_modules
Why didn't chelper auth reload update the MCP config? To find out, I opened up the CLI's source code located in node_modules/@z_ai/coding-helper/dist/lib/claude-code-manager.js.
I tracked down the exact function responsible for reloading the configuration:
loadGLMConfig(plan, apiKey) {
// 1. Ensure onboarding is completed
this.ensureOnboardingCompleted();
// 2. Clean up shell environment variables
this.cleanupShellEnvVars();
// 3. Update ANTHROPIC_AUTH_TOKEN in settings.json
this.saveSettings(glmConfig);
}
The flaw in the architecture was immediately obvious.
Claude Code actually splits its configuration into two distinct files:
-
~/.claude/settings.json(Used for standard Claude API calls) -
~/.claude.json(Used to register MCP servers and their auth headers)
The developer of the CLI tool only programmed this.saveSettings() to update the standard API token. The tool completely ignored the MCP server configurations. As a result, any HTTP-based MCP tools registered previously were left stranded with expired credentials.
The Fix
If you are dealing with a similar misbehaving CLI wrapper, you can't rely on its reload command. You have a few options:
Option 1: The Nuclear Option (Recommended)
Run the initialization command again from scratch. This usually forces the tool to overwrite all files completely.
npx @z_ai/coding-helper init
Option 2: The Python Automation Fix
If you have heavily customized your mcpServers and don't want to reset everything, I wrote a quick Python script to automatically hunt down HTTP-based MCP tools in your config and update their headers securely:
import json
import os
config_path = os.path.expanduser('~/.claude.json')
new_key = "YOUR_NEW_VALID_API_KEY"
try:
with open(config_path, 'r') as f:
config = json.load(f)
# Iterate through all configured MCP servers
for mcp_name, mcp_config in config.get('mcpServers', {}).items():
# Update only those that use HTTP headers for authentication
if 'headers' in config.get('mcpServers', {}).get(mcp_name, {}):
config['mcpServers'][mcp_name]['headers']['Authorization'] = f'Bearer {new_key}'
print(f"Updated auth token for MCP tool: {mcp_name}")
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
print("All MCP keys updated successfully!")
except FileNotFoundError:
print("Error: ~/.claude.json configuration file not found.")
The SDET Takeaway
This is why Software Development Engineers in Test (SDETs) don't just rely on UI green lights or standard CLI outputs.
When an automation tool tells you "Success!" but the system behaves as if it failed, there is almost always a state synchronization issue underneath. The abstraction layers designed to help us can quickly become blindspots.
The Golden Rule of Debugging Toolchains: When system behavior contradicts configuration state, stop trusting the management tools and start reading the raw .json and .yaml files.
Top comments (0)