Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions crates/goose/src/providers/claude_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,42 @@ impl Provider for ClaudeCodeProvider {
}
Some("result") => {
process.needs_drain = false;
if parsed
.get("is_error")
.and_then(Value::as_bool)
.unwrap_or(false)
{
let subtype = parsed
.get("subtype")
.and_then(Value::as_str)
.unwrap_or("error");
let mut details = Vec::new();
if let Some(error) =
parsed.get("error").and_then(Value::as_str)
{
details.push(error);
}
if let Some(errors) =
parsed.get("errors").and_then(Value::as_array)
{
details.extend(errors.iter().filter_map(Value::as_str));
}
if let Some(result) =
parsed.get("result").and_then(Value::as_str)
{
details.push(result);
}
let details = details.join("; ");
let message = match (subtype, details.is_empty()) {
("success", false) => details,
(_, false) => format!("{subtype}: {details}"),
_ => subtype.to_string(),
};
stream_error = Some(ProviderError::RequestFailed(format!(
"Claude CLI error: {message}"
)));
break;
}
if let Some(usage_info) = parsed.get("usage") {
let new = extract_usage_tokens(usage_info);
let reports_own_cache = new.cache_read_input_tokens.is_some()
Expand Down
6 changes: 3 additions & 3 deletions ui/desktop/tests/integration/test_providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* and validates the output.
*/

import { expect, beforeAll } from 'vitest';
import { beforeAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
Expand All @@ -29,7 +29,7 @@ beforeAll(() => {

const { testAgentic, testNonAgentic } = providerTest(discoverTestCases());

testNonAgentic('reads files via shell tool', async (tc) => {
testNonAgentic('reads files via shell tool', async (tc, { expect }) => {
const testdir = fs.mkdtempSync(path.join(os.tmpdir(), 'goose-test-'));
try {
const tokenA = `smoke-alpha-${Math.floor(Math.random() * 32768)}`;
Expand Down Expand Up @@ -68,7 +68,7 @@ testNonAgentic('reads files via shell tool', async (tc) => {
}
});

testAgentic('reads file contents', async (tc) => {
testAgentic('reads file contents', async (tc, { expect }) => {
const testdir = fs.mkdtempSync(path.join(os.tmpdir(), 'goose-test-'));
try {
fs.copyFileSync(testFile, path.join(testdir, 'test-content.txt'));
Expand Down
4 changes: 2 additions & 2 deletions ui/desktop/tests/integration/test_providers_code_exec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* that the code_execution tool was invoked.
*/

import { expect, beforeAll } from 'vitest';
import { beforeAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
Expand All @@ -22,7 +22,7 @@ beforeAll(() => {

const { testAll } = providerTest(discoverTestCases({ skipAgentic: true }));

testAll('invokes code_execution tool', async (tc) => {
testAll('invokes code_execution tool', async (tc, { expect }) => {
const testdir = fs.mkdtempSync(path.join(os.tmpdir(), 'goose-codeexec-'));
try {
const output = await runGoose(
Expand Down
20 changes: 10 additions & 10 deletions ui/desktop/tests/integration/test_providers_lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* allowed-failure list, agentic-provider list, and environment detection.
*/

import { test } from 'vitest';
import { test, type TestContext } from 'vitest';
import { execSync, spawn, type ChildProcess } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
Expand Down Expand Up @@ -299,32 +299,32 @@ export function discoverTestCases(options?: { skipAgentic?: boolean }): TestCase
// Test registration helpers
// ---------------------------------------------------------------------------

type ProviderTestFn = (tc: TestCase) => Promise<void>;
type ProviderTestFn = (tc: TestCase, context: TestContext) => Promise<void>;

function registerTests(label: string, cases: TestCase[], fn: ProviderTestFn): void {
const available = cases.filter((tc) => tc.available && !tc.flaky);
const flaky = cases.filter((tc) => tc.available && tc.flaky);
const skipped = cases.filter((tc) => !tc.available);

if (available.length > 0) {
test.each(available)(`${label} — $provider / $model`, async (tc) => {
await fn(tc);
test.concurrent.for(available)(`${label} — $provider / $model`, async (tc, context) => {
await fn(tc, context);
});
}

if (flaky.length > 0) {
// Use a longer vitest timeout (90s) so the internal runGoose timeout (55s)
// fires first — that rejection is catchable and the test passes as "allowed".
test.each(flaky)(
test.concurrent.for(flaky)(
`${label} — $provider / $model (flaky)`,
async (tc) => {
{ timeout: 90_000 },
async (tc, context) => {
try {
await fn(tc);
await fn(tc, context);
} catch (err) {
console.warn(`Flaky test ${tc.provider}/${tc.model} failed (allowed): ${err}`);
}
},
90_000
}
);
}

Expand Down Expand Up @@ -373,7 +373,7 @@ export function runGoose(
['run', '--text', prompt, '--with-builtin', builtins],
{
cwd,
env: { ...process.env, ...env },
env: { ...process.env, ...env, GOOSE_MODE: 'auto' },
stdio: ['ignore', 'pipe', 'pipe'],
}
);
Expand Down
1 change: 1 addition & 0 deletions ui/desktop/vitest.integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig({
hookTimeout: 60000,
pool: 'forks',
singleFork: true,
maxConcurrency: 4,
silent: 'passed-only',
},
});
Loading