Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
e7ff151c04 Disable ANSI colors and browser prefix in AI agent mode
This ensures that grep '^error:' works reliably in all scenarios:
- ANSI escape codes are disabled to prevent them from appearing before 'error:'
- The 'frontend' prefix is suppressed so browser errors also start with 'error:'

These changes guarantee that the error prefix is always at column 0 for
easy grepping by AI agents.
2025-10-12 15:30:53 +00:00
Claude Bot
eeb8ae5d03 Fix comment to reference correct location of normalization 2025-10-12 15:11:57 +00:00
Claude Bot
4a19e58cf9 Show 'error:' prefix with name property in AI agent mode
When Output.isAIAgent() returns true (i.e., when running under Claude Code
or similar AI tools), error messages now consistently use "error:" as the
prefix instead of the specific error type (TypeError, ReferenceError, etc.).
The actual error type is shown as a property below the error message.

This allows AI agents to reliably search for errors using a simple pattern:
  command 2>&1 | grep "^error:"
instead of needing to match multiple patterns like:
  grep -E "^(Error|TypeError|ReferenceError|SyntaxError|RangeError|...):"

Before (AI agent mode):
  TypeError: This is a test error

After (AI agent mode):
  error: This is a test error
   name: "TypeError"

This provides a more consistent and parseable format for AI tools while
still preserving all error information.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 14:57:53 +00:00

View File

@@ -1958,7 +1958,8 @@ pub fn printException(
.stack_check = bun.StackCheck.init(),
};
defer formatter.deinit();
if (Output.enable_ansi_colors) {
// Disable ANSI colors in AI agent mode to ensure grep '^error:' works reliably
if (Output.enable_ansi_colors and !Output.isAIAgent()) {
this.printErrorlikeObject(exception.value(), exception, exception_list, &formatter, Writer, writer, true, allow_side_effects);
} else {
this.printErrorlikeObject(exception.value(), exception, exception_list, &formatter, Writer, writer, false, allow_side_effects);
@@ -1997,7 +1998,8 @@ pub noinline fn runErrorHandler(this: *VirtualMachine, result: JSValue, exceptio
.error_display_level = .full,
};
defer formatter.deinit();
switch (Output.enable_ansi_colors) {
// Disable ANSI colors in AI agent mode to ensure grep '^error:' works reliably
switch (Output.enable_ansi_colors and !Output.isAIAgent()) {
inline else => |enable_colors| this.printErrorlikeObject(result, null, exception_list, &formatter, @TypeOf(writer), writer, enable_colors, true),
}
}
@@ -3113,8 +3115,30 @@ fn printErrorInstance(
const error_obj = error_instance.getObject().?;
var iterator = try Iterator.init(this.global, error_obj);
defer iterator.deinit();
const longest_name = @min(iterator.getLongestPropertyName(), 10);
var longest_name_base = iterator.getLongestPropertyName();
// In AI agent mode, we'll also print the "name" field if it's not "Error".
// Since we normalize all errors to "error:" prefix in printErrorNameAndMessage, we need to preserve
// the actual error type (TypeError, ReferenceError, etc.) as a property.
// This provides structured error information while maintaining grepability.
if (Output.isAIAgent() and !name.eqlComptime("Error")) {
longest_name_base = @max(longest_name_base, "name".len);
}
const longest_name = @min(longest_name_base, 10);
var is_first_property = true;
// In AI agent mode, print the name property first if it's not "Error".
// Example output:
// error: This is a test error
// name: "TypeError"
// This way AI agents can grep for "^error:" to find all errors, then
// parse the "name:" field to determine the specific error type.
if (Output.isAIAgent() and !name.eqlComptime("Error")) {
const pad_left = longest_name -| "name".len;
try writer.writeByteNTimes(' ', pad_left);
try writer.print(comptime Output.prettyFmt(" name<r><d>:<r> ", allow_ansi_color), .{});
try writer.print(comptime Output.prettyFmt("\"{}\"\n", allow_ansi_color), .{name});
is_first_property = false;
}
while (try iterator.next()) |field| {
const value = iterator.value;
if (field.eqlComptime("message") or field.eqlComptime("name") or field.eqlComptime("stack")) {
@@ -3284,11 +3308,19 @@ fn printErrorNameAndMessage(
comptime allow_ansi_color: bool,
error_display_level: ConsoleObject.FormatOptions.ErrorDisplayLevel,
) !void {
if (is_browser_error) {
if (is_browser_error and !Output.isAIAgent()) {
try writer.writeAll(Output.prettyFmt("<red>frontend<r> ", true));
}
if (!name.isEmpty() and !message.isEmpty()) {
const display_name, const display_message = if (name.eqlComptime("Error")) brk: {
const display_name, const display_message = if (Output.isAIAgent()) brk: {
// In AI agent mode, always use "error:" prefix regardless of error type.
// This allows AI agents to reliably search for errors using:
// command 2>&1 | grep "^error:"
// instead of needing to match multiple patterns like:
// grep -E "^(Error|TypeError|ReferenceError|SyntaxError|RangeError|...):"
// The actual error name (e.g., "TypeError") is shown as a property below.
break :brk .{ String.empty, message };
} else if (name.eqlComptime("Error")) brk: {
// If `err.code` is set, and `err.message` is of form `{code}: {text}`,
// use the code as the name since `error: ENOENT: no such ...` is
// not as nice looking since it there are two error prefixes.