DEV Community

David Yan
David Yan

Posted on

Debugging a CLI Tool Blindspot: Why "Reload" Commands Don't Always Reload Everything

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
Enter fullscreen mode Exit fullscreen mode

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."}}
Enter fullscreen mode Exit fullscreen mode

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:

  1. The CLI helper's main config (~/.chelper/config.yaml): This contained my brand new, valid API key.
  2. Claude Code's MCP config (~/.claude.json): Looking at the mcpServers object, 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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

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)