Merge pull request #486 from AutoMaker-Org/fix/claude-usage-parsing

fix: Claude usage parsing for CLI v2.x and trust prompt handling
This commit is contained in:
Shirone
2026-01-14 16:56:26 +00:00
committed by GitHub
4 changed files with 74 additions and 22 deletions

View File

@@ -34,6 +34,13 @@ export function createClaudeRoutes(service: ClaudeUsageService): Router {
error: 'Authentication required',
message: "Please run 'claude login' to authenticate",
});
} else if (message.includes('TRUST_PROMPT_PENDING')) {
// Trust prompt appeared but couldn't be auto-approved
res.status(200).json({
error: 'Trust prompt pending',
message:
'Claude CLI needs folder permission. Please run "claude" in your terminal and approve access.',
});
} else if (message.includes('timed out')) {
res.status(200).json({
error: 'Command timed out',

View File

@@ -49,13 +49,11 @@ export class ClaudeUsageService {
/**
* Execute the claude /usage command and return the output
* Uses platform-specific PTY implementation
* Uses node-pty on all platforms for consistency
*/
private executeClaudeUsageCommand(): Promise<string> {
if (this.isWindows || this.isLinux) {
return this.executeClaudeUsageCommandPty();
}
return this.executeClaudeUsageCommandMac();
// Use node-pty on all platforms - it's more reliable than expect on macOS
return this.executeClaudeUsageCommandPty();
}
/**
@@ -67,24 +65,36 @@ export class ClaudeUsageService {
let stderr = '';
let settled = false;
// Use a simple working directory (home or tmp)
const workingDirectory = process.env.HOME || '/tmp';
// Use current working directory - likely already trusted by Claude CLI
const workingDirectory = process.cwd();
// Use 'expect' with an inline script to run claude /usage with a PTY
// Wait for "Current session" header, then wait for full output before exiting
// Running from cwd which should already be trusted
const expectScript = `
set timeout 20
set timeout 30
spawn claude /usage
# Wait for usage data or handle trust prompt if needed
expect {
"Current session" {
sleep 2
send "\\x1b"
-re "Ready to code|permission to work|Do you want to work" {
# Trust prompt appeared - send Enter to approve
sleep 1
send "\\r"
exp_continue
}
"Esc to cancel" {
"Current session" {
# Usage data appeared - wait for full output, then exit
sleep 3
send "\\x1b"
}
timeout {}
"% left" {
# Usage percentage appeared
sleep 3
send "\\x1b"
}
timeout {
send "\\x1b"
}
eof {}
}
expect eof
@@ -158,10 +168,10 @@ export class ClaudeUsageService {
let output = '';
let settled = false;
let hasSeenUsageData = false;
let hasSeenTrustPrompt = false;
const workingDirectory = this.isWindows
? process.env.USERPROFILE || os.homedir() || 'C:\\'
: os.tmpdir();
// Use current working directory (project dir) - most likely already trusted by Claude CLI
const workingDirectory = process.cwd();
// Use platform-appropriate shell and command
const shell = this.isWindows ? 'cmd.exe' : '/bin/sh';
@@ -206,6 +216,13 @@ export class ClaudeUsageService {
// Don't fail if we have data - return it instead
if (output.includes('Current session')) {
resolve(output);
} else if (hasSeenTrustPrompt) {
// Trust prompt was shown but we couldn't auto-approve it
reject(
new Error(
'TRUST_PROMPT_PENDING: Claude CLI is waiting for folder permission. Please run "claude" in your terminal and approve access to continue.'
)
);
} else {
reject(
new Error(
@@ -269,10 +286,18 @@ export class ClaudeUsageService {
}, 3000);
}
// Handle Trust Dialog: "Do you want to work in this folder?"
// Since we are running in os.tmpdir(), it is safe to approve.
if (!hasApprovedTrust && cleanOutput.includes('Do you want to work in this folder?')) {
// Handle Trust Dialog - multiple variants:
// - "Do you want to work in this folder?"
// - "Ready to code here?" / "I'll need permission to work with your files"
// Since we are running in cwd (project dir), it is safe to approve.
if (
!hasApprovedTrust &&
(cleanOutput.includes('Do you want to work in this folder?') ||
cleanOutput.includes('Ready to code here') ||
cleanOutput.includes('permission to work with your files'))
) {
hasApprovedTrust = true;
hasSeenTrustPrompt = true;
// Wait a tiny bit to ensure prompt is ready, then send Enter
setTimeout(() => {
if (!settled && ptyProcess && !ptyProcess.killed) {

View File

@@ -11,6 +11,7 @@ import { useSetupStore } from '@/store/setup-store';
const ERROR_CODES = {
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
AUTH_ERROR: 'AUTH_ERROR',
TRUST_PROMPT: 'TRUST_PROMPT',
UNKNOWN: 'UNKNOWN',
} as const;
@@ -55,8 +56,12 @@ export function ClaudeUsagePopover() {
}
const data = await api.claude.getUsage();
if ('error' in data) {
// Detect trust prompt error
const isTrustPrompt =
data.error === 'Trust prompt pending' ||
(data.message && data.message.includes('folder permission'));
setError({
code: ERROR_CODES.AUTH_ERROR,
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
message: data.message || data.error,
});
return;
@@ -257,6 +262,11 @@ export function ClaudeUsagePopover() {
<p className="text-xs text-muted-foreground">
{error.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
'Ensure the Electron bridge is running or restart the app'
) : error.code === ERROR_CODES.TRUST_PROMPT ? (
<>
Run <code className="font-mono bg-muted px-1 rounded">claude</code> in your
terminal and approve access to continue
</>
) : (
<>
Make sure Claude CLI is installed and authenticated via{' '}

View File

@@ -14,6 +14,7 @@ const ERROR_CODES = {
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
AUTH_ERROR: 'AUTH_ERROR',
NOT_AVAILABLE: 'NOT_AVAILABLE',
TRUST_PROMPT: 'TRUST_PROMPT',
UNKNOWN: 'UNKNOWN',
} as const;
@@ -108,8 +109,12 @@ export function UsagePopover() {
}
const data = await api.claude.getUsage();
if ('error' in data) {
// Detect trust prompt error
const isTrustPrompt =
data.error === 'Trust prompt pending' ||
(data.message && data.message.includes('folder permission'));
setClaudeError({
code: ERROR_CODES.AUTH_ERROR,
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
message: data.message || data.error,
});
return;
@@ -404,6 +409,11 @@ export function UsagePopover() {
<p className="text-xs text-muted-foreground">
{claudeError.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
'Ensure the Electron bridge is running or restart the app'
) : claudeError.code === ERROR_CODES.TRUST_PROMPT ? (
<>
Run <code className="font-mono bg-muted px-1 rounded">claude</code> in
your terminal and approve access to continue
</>
) : (
<>
Make sure Claude CLI is installed and authenticated via{' '}