mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
fix: Claude usage parsing for CLI v2.x and trust prompt handling
- Use node-pty on all platforms instead of expect on macOS for more reliable PTY handling
- Use process.cwd() as working directory (project dir is likely already trusted)
- Add detection for new trust prompt text variants ("Ready to code here", "permission to work")
- Add specific error handling for trust prompt pending state
- Show helpful UI message when trust prompt needs manual approval
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,13 @@ export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
|||||||
error: 'Authentication required',
|
error: 'Authentication required',
|
||||||
message: "Please run 'claude login' to authenticate",
|
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')) {
|
} else if (message.includes('timed out')) {
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
error: 'Command timed out',
|
error: 'Command timed out',
|
||||||
|
|||||||
@@ -49,13 +49,11 @@ export class ClaudeUsageService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the claude /usage command and return the output
|
* 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> {
|
private executeClaudeUsageCommand(): Promise<string> {
|
||||||
if (this.isWindows || this.isLinux) {
|
// Use node-pty on all platforms - it's more reliable than expect on macOS
|
||||||
return this.executeClaudeUsageCommandPty();
|
return this.executeClaudeUsageCommandPty();
|
||||||
}
|
|
||||||
return this.executeClaudeUsageCommandMac();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,24 +65,36 @@ export class ClaudeUsageService {
|
|||||||
let stderr = '';
|
let stderr = '';
|
||||||
let settled = false;
|
let settled = false;
|
||||||
|
|
||||||
// Use a simple working directory (home or tmp)
|
// Use current working directory - likely already trusted by Claude CLI
|
||||||
const workingDirectory = process.env.HOME || '/tmp';
|
const workingDirectory = process.cwd();
|
||||||
|
|
||||||
// Use 'expect' with an inline script to run claude /usage with a PTY
|
// 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 = `
|
const expectScript = `
|
||||||
set timeout 20
|
set timeout 30
|
||||||
spawn claude /usage
|
spawn claude /usage
|
||||||
|
|
||||||
|
# Wait for usage data or handle trust prompt if needed
|
||||||
expect {
|
expect {
|
||||||
"Current session" {
|
-re "Ready to code|permission to work|Do you want to work" {
|
||||||
sleep 2
|
# Trust prompt appeared - send Enter to approve
|
||||||
send "\\x1b"
|
sleep 1
|
||||||
|
send "\\r"
|
||||||
|
exp_continue
|
||||||
}
|
}
|
||||||
"Esc to cancel" {
|
"Current session" {
|
||||||
|
# Usage data appeared - wait for full output, then exit
|
||||||
sleep 3
|
sleep 3
|
||||||
send "\\x1b"
|
send "\\x1b"
|
||||||
}
|
}
|
||||||
timeout {}
|
"% left" {
|
||||||
|
# Usage percentage appeared
|
||||||
|
sleep 3
|
||||||
|
send "\\x1b"
|
||||||
|
}
|
||||||
|
timeout {
|
||||||
|
send "\\x1b"
|
||||||
|
}
|
||||||
eof {}
|
eof {}
|
||||||
}
|
}
|
||||||
expect eof
|
expect eof
|
||||||
@@ -158,10 +168,12 @@ export class ClaudeUsageService {
|
|||||||
let output = '';
|
let output = '';
|
||||||
let settled = false;
|
let settled = false;
|
||||||
let hasSeenUsageData = false;
|
let hasSeenUsageData = false;
|
||||||
|
let hasSeenTrustPrompt = false;
|
||||||
|
|
||||||
|
// Use current working directory (project dir) - most likely already trusted by Claude CLI
|
||||||
const workingDirectory = this.isWindows
|
const workingDirectory = this.isWindows
|
||||||
? process.env.USERPROFILE || os.homedir() || 'C:\\'
|
? process.env.USERPROFILE || os.homedir() || 'C:\\'
|
||||||
: os.tmpdir();
|
: process.cwd();
|
||||||
|
|
||||||
// Use platform-appropriate shell and command
|
// Use platform-appropriate shell and command
|
||||||
const shell = this.isWindows ? 'cmd.exe' : '/bin/sh';
|
const shell = this.isWindows ? 'cmd.exe' : '/bin/sh';
|
||||||
@@ -206,6 +218,13 @@ export class ClaudeUsageService {
|
|||||||
// Don't fail if we have data - return it instead
|
// Don't fail if we have data - return it instead
|
||||||
if (output.includes('Current session')) {
|
if (output.includes('Current session')) {
|
||||||
resolve(output);
|
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 {
|
} else {
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
@@ -269,10 +288,18 @@ export class ClaudeUsageService {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Trust Dialog: "Do you want to work in this folder?"
|
// Handle Trust Dialog - multiple variants:
|
||||||
// Since we are running in os.tmpdir(), it is safe to approve.
|
// - "Do you want to work in this folder?"
|
||||||
if (!hasApprovedTrust && cleanOutput.includes('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;
|
hasApprovedTrust = true;
|
||||||
|
hasSeenTrustPrompt = true;
|
||||||
// Wait a tiny bit to ensure prompt is ready, then send Enter
|
// Wait a tiny bit to ensure prompt is ready, then send Enter
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!settled && ptyProcess && !ptyProcess.killed) {
|
if (!settled && ptyProcess && !ptyProcess.killed) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useSetupStore } from '@/store/setup-store';
|
|||||||
const ERROR_CODES = {
|
const ERROR_CODES = {
|
||||||
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
||||||
AUTH_ERROR: 'AUTH_ERROR',
|
AUTH_ERROR: 'AUTH_ERROR',
|
||||||
|
TRUST_PROMPT: 'TRUST_PROMPT',
|
||||||
UNKNOWN: 'UNKNOWN',
|
UNKNOWN: 'UNKNOWN',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -55,8 +56,12 @@ export function ClaudeUsagePopover() {
|
|||||||
}
|
}
|
||||||
const data = await api.claude.getUsage();
|
const data = await api.claude.getUsage();
|
||||||
if ('error' in data) {
|
if ('error' in data) {
|
||||||
|
// Detect trust prompt error
|
||||||
|
const isTrustPrompt =
|
||||||
|
data.error === 'Trust prompt pending' ||
|
||||||
|
(data.message && data.message.includes('folder permission'));
|
||||||
setError({
|
setError({
|
||||||
code: ERROR_CODES.AUTH_ERROR,
|
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
|
||||||
message: data.message || data.error,
|
message: data.message || data.error,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -257,6 +262,11 @@ export function ClaudeUsagePopover() {
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{error.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
{error.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
||||||
'Ensure the Electron bridge is running or restart the app'
|
'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{' '}
|
Make sure Claude CLI is installed and authenticated via{' '}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const ERROR_CODES = {
|
|||||||
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
API_BRIDGE_UNAVAILABLE: 'API_BRIDGE_UNAVAILABLE',
|
||||||
AUTH_ERROR: 'AUTH_ERROR',
|
AUTH_ERROR: 'AUTH_ERROR',
|
||||||
NOT_AVAILABLE: 'NOT_AVAILABLE',
|
NOT_AVAILABLE: 'NOT_AVAILABLE',
|
||||||
|
TRUST_PROMPT: 'TRUST_PROMPT',
|
||||||
UNKNOWN: 'UNKNOWN',
|
UNKNOWN: 'UNKNOWN',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -108,8 +109,12 @@ export function UsagePopover() {
|
|||||||
}
|
}
|
||||||
const data = await api.claude.getUsage();
|
const data = await api.claude.getUsage();
|
||||||
if ('error' in data) {
|
if ('error' in data) {
|
||||||
|
// Detect trust prompt error
|
||||||
|
const isTrustPrompt =
|
||||||
|
data.error === 'Trust prompt pending' ||
|
||||||
|
(data.message && data.message.includes('folder permission'));
|
||||||
setClaudeError({
|
setClaudeError({
|
||||||
code: ERROR_CODES.AUTH_ERROR,
|
code: isTrustPrompt ? ERROR_CODES.TRUST_PROMPT : ERROR_CODES.AUTH_ERROR,
|
||||||
message: data.message || data.error,
|
message: data.message || data.error,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -404,6 +409,11 @@ export function UsagePopover() {
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{claudeError.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
{claudeError.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
||||||
'Ensure the Electron bridge is running or restart the app'
|
'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{' '}
|
Make sure Claude CLI is installed and authenticated via{' '}
|
||||||
|
|||||||
Reference in New Issue
Block a user