Skip to content

fix: colorize stderr warnings and errors on TTY terminals#394

Open
seang1121 wants to merge 3 commits intogoogleworkspace:mainfrom
seang1121:fix/317-colorize-stderr-tty
Open

fix: colorize stderr warnings and errors on TTY terminals#394
seang1121 wants to merge 3 commits intogoogleworkspace:mainfrom
seang1121:fix/317-colorize-stderr-tty

Conversation

@seang1121
Copy link
Contributor

Closes #317

Summary

  • Adds three pub(crate) ANSI color helpers (yellow, red, bold) and an is_tty() check to src/error.rs
  • Colors are only applied when stderr is a TTY (std::io::IsTerminal); plain text is emitted otherwise (pipes, CI, redirected output)
  • No new dependencies — uses the IsTerminal trait already available on the Rust stdlib (stabilized 1.70)

Changed call sites:

File Message Color
src/error.rs 💡 API not enabled… (accessNotConfigured) yellow
src/main.rs warning: unknown output format yellow (warning: prefix)
src/executor.rs ⚠️ Model Armor: prompt injection detected yellow
src/executor.rs ⚠️ Model Armor sanitization failed yellow

Test plan

  • Run cargo test — new unit tests in error.rs verify helpers return plain strings in non-TTY environments
  • Run gws drive files list --format badformat in a real terminal and confirm warning: appears in yellow
  • Pipe output (gws drive files list --format badformat 2>&1 | cat) and confirm no ANSI escape codes appear

@seang1121 seang1121 requested a review from jpoehnelt as a code owner March 11, 2026 01:20
@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 9267f3f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added area: http area: core Core CLI parsing, commands, error handling, utilities labels Mar 11, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the user experience by introducing colorized output for warnings and errors displayed on stderr in TTY terminals. It implements a mechanism to detect if stderr is connected to a TTY and applies ANSI escape codes for yellow and red coloring, along with bold formatting, only in such environments. This ensures that important messages are visually distinct for users interacting directly with the command-line interface, while maintaining compatibility with automated systems or redirected output by emitting plain text.

Highlights

  • ANSI Color Helpers and TTY Detection: Introduced is_tty() function and yellow, red, bold ANSI color helper functions in src/error.rs to conditionally apply colors based on whether stderr is a TTY.
  • Conditional Color Application: Ensured that ANSI colors are only applied when stderr is a TTY, falling back to plain text output for non-TTY environments like pipes or CI.
  • Warning and Error Message Colorization: Updated specific warning and error messages across src/error.rs, src/executor.rs, and src/main.rs to utilize the new yellow coloring for improved visibility.
  • Unit Tests for Color Helpers: Added unit tests in src/error.rs to verify that the color helper functions correctly return plain strings when stderr is not a TTY.
Changelog
  • .changeset/colorize-stderr-tty.md
    • Added a new changeset file describing the colorization of stderr warnings and errors.
  • src/error.rs
    • Added is_tty() function to check if stderr is a terminal.
    • Implemented yellow(), red(), and bold() helper functions for ANSI color formatting.
    • Updated the "API not enabled" error message to use the yellow() helper.
    • Added new unit tests for yellow, red, and bold functions to verify non-TTY behavior.
  • src/executor.rs
    • Modified prompt injection detection message to be yellow.
    • Modified model armor sanitization failure message to be yellow.
  • src/main.rs
    • Updated the "unknown output format" warning message to be yellow.
Activity
  • Implemented new pub(crate) ANSI color helpers (yellow, red, bold) and an is_tty() check in src/error.rs.
  • Applied colorization logic to specific warning and error messages in src/error.rs, src/executor.rs, and src/main.rs.
  • Added unit tests in error.rs to verify color helpers return plain strings in non-TTY environments.
  • Provided a test plan including running cargo test, checking colored output in a real terminal, and confirming no ANSI codes in piped output.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces colorized output for warnings and errors on TTY terminals, which is a nice enhancement for user experience. The implementation correctly uses std::io::IsTerminal to detect TTYs and adds helper functions for different colors. The changes are applied to several error/warning messages across the codebase. My main feedback is to refactor the new color helper functions to reduce code duplication, which will improve long-term maintainability.

src/error.rs Outdated
Comment on lines +23 to +45
pub(crate) fn yellow(s: &str) -> String {
if is_tty() {
format!("\x1b[33m{s}\x1b[0m")
} else {
s.to_string()
}
}

pub(crate) fn red(s: &str) -> String {
if is_tty() {
format!("\x1b[31m{s}\x1b[0m")
} else {
s.to_string()
}
}

pub(crate) fn bold(s: &str) -> String {
if is_tty() {
format!("\x1b[1m{s}\x1b[0m")
} else {
s.to_string()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There's some code duplication across the yellow, red, and bold functions. The logic for checking is_tty() and formatting the string is identical in all three. You can refactor this into a single private helper function to improve maintainability. This way, if the logic needs to change in the future, you'll only have to update it in one place.

fn colorize(s: &str, code: &str) -> String {
    if is_tty() {
        format!("\x1b[{code}m{s}\x1b[0m")
    } else {
        s.to_string()
    }
}

pub(crate) fn yellow(s: &str) -> String {
    colorize(s, "33")
}

pub(crate) fn red(s: &str) -> String {
    colorize(s, "31")
}

pub(crate) fn bold(s: &str) -> String {
    colorize(s, "1")
}

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces colored output for warnings and errors on TTY terminals, which is a great enhancement for user experience. The implementation correctly checks for TTY before adding ANSI color codes. My review includes one high-severity suggestion in src/executor.rs to prevent potential ANSI injection from error messages, making the implementation safer and more consistent with other changes in this PR.

src/executor.rs Outdated
}
Err(e) => {
eprintln!("⚠️ Model Armor sanitization failed: {e}");
eprintln!("{}", crate::error::yellow(&format!("⚠️ Model Armor sanitization failed: {e}")));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Coloring the entire formatted string, which includes the error e, could lead to ANSI injection if the error's Display implementation contains malicious escape codes. This could corrupt terminal output.

A safer approach, consistent with the change in src/main.rs, is to only color the static part of the warning message.

Suggested change
eprintln!("{}", crate::error::yellow(&format!("⚠️ Model Armor sanitization failed: {e}")));
eprintln!("{} {e}", crate::error::yellow("⚠️ Model Armor sanitization failed:"));

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a nice usability improvement by colorizing warnings and errors on TTY terminals. The implementation is clean and avoids new dependencies by using the Rust standard library. I've identified a potential security vulnerability in the new colorize function that should be addressed. Additionally, I have a suggestion to improve the newly added tests for better clarity and maintainability. Overall, this is a great addition.

Comment on lines +23 to +29
fn colorize(s: &str, code: &str) -> String {
if is_tty() {
format!("\x1b[{code}m{s}\x1b[0m")
} else {
s.to_string()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The colorize function directly includes the input string s in the formatted output. If a string containing ANSI escape codes is passed to yellow, red, or bold, it could lead to terminal escape injection. This would allow an attacker to manipulate the terminal's output, or in some terminals, execute commands. While the current usages in this PR are safe as they use static strings, future uses with dynamic or user-controlled data could introduce a vulnerability. To mitigate this, the input string should be sanitized to remove any existing escape codes before being wrapped in new ones.

Suggested change
fn colorize(s: &str, code: &str) -> String {
if is_tty() {
format!("\x1b[{code}m{s}\x1b[0m")
} else {
s.to_string()
}
}
fn colorize(s: &str, code: &str) -> String {
if is_tty() {
// Basic sanitization to prevent terminal escape injection.
// This removes the ESC character, which is the start of most control sequences.
let sanitized_s = s.replace('\x1b', "");
format!("\x1b[{code}m{sanitized_s}\x1b[0m")
} else {
s.to_string()
}
}

Comment on lines +146 to +165
// In test environments stderr is not a TTY, so the helpers return plain strings.
#[test]
fn test_yellow_non_tty() {
// Stderr is not a TTY in CI/test runners; plain string is returned.
let result = yellow("hello");
// Either plain or ANSI-wrapped depending on environment; must contain the text.
assert!(result.contains("hello"));
}

#[test]
fn test_red_non_tty() {
let result = red("error");
assert!(result.contains("error"));
}

#[test]
fn test_bold_non_tty() {
let result = bold("important");
assert!(result.contains("important"));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The tests test_yellow_non_tty, test_red_non_tty, and test_bold_non_tty are redundant and their assertions are too weak to be meaningful. The assert!(result.contains(...)) check passes even if the string is colorized, so it doesn't verify the "plain string is returned" behavior mentioned in the comment. The test_color_helpers_no_ansi_when_non_tty test already covers this scenario correctly and more robustly. To improve test suite clarity, correctness, and maintainability, these three redundant tests should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Core CLI parsing, commands, error handling, utilities area: http

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Display colored error/warning messages in TTY terminals

2 participants