Skip to content

fix: error handler when not a TTY#77

Merged
caarlos0 merged 1 commit intomainfrom
err-term
Sep 30, 2025
Merged

fix: error handler when not a TTY#77
caarlos0 merged 1 commit intomainfrom
err-term

Conversation

@caarlos0
Copy link
Copy Markdown
Contributor

@caarlos0 caarlos0 commented Sep 30, 2025

the current implementation assumes the user-provided error handler will require a tty, which might not be true at al.

case in point: goreleaser. Our error handler extracts some extra information from the error and handles it, and it does not care about whether it's a terminal or not.

without this change, an error could go without being properly handled because fang decided to simply not call the error handler.

instead, I think this check should be inside fang's default error handler, which is where it was probably supposed to be anyway.

@caarlos0 caarlos0 self-assigned this Sep 30, 2025
@caarlos0 caarlos0 force-pushed the err-term branch 3 times, most recently from 7659a78 to dfcf3af Compare September 30, 2025 04:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors error handling to separate TTY and non-TTY error handlers, allowing callers to provide different handlers for terminal and non-terminal outputs. The motivation is that the previous implementation incorrectly assumed all custom error handlers would require TTY functionality, which prevented proper error handling in tools like goreleaser that don't require terminal features.

Key changes:

  • Introduces separate TtyErrorHandler and NonTtyErrorHandler types
  • Deprecates the original ErrorHandler type and related functions
  • Moves terminal detection logic into the execution flow rather than assuming it in custom handlers

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
help.go Adds new TTY/non-TTY error handler functions and deprecates the original handler
fang.go Refactors settings to use separate TTY/non-TTY handlers and updates execution logic
Comments suppressed due to low confidence (1)

help.go:1

  • The comment is incorrect. This function sets the error handler for when output IS a TTY, not when it's NOT a TTY.
package fang

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@caarlos0
Copy link
Copy Markdown
Contributor Author

I also wonder if NonTTY can have a better name 🤔

@caarlos0 caarlos0 changed the title fix: move terminal check into default error handler fix: error handler when not a TTY Sep 30, 2025
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@caarlos0 caarlos0 requested a review from Copilot September 30, 2025 14:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Copy Markdown
Member

@aymanbagabas aymanbagabas left a comment

Choose a reason for hiding this comment

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

Why don't you keep a single error handler and check if it's a TTY in the handler?

@caarlos0
Copy link
Copy Markdown
Contributor Author

Why don't you keep a single error handler and check if it's a TTY in the handler?

that would be ideal, but would also require a breaking change, as currently the error handler expects the styles etc

@aymanbagabas
Copy link
Copy Markdown
Member

that would be ideal, but would also require a breaking change, as currently the error handler expects the styles etc

if noTTY { fmt.Fprint(w, noStyles) }, no?

@meowgorithm
Copy link
Copy Markdown
Member

What's the best way to test this one?

Also I'd probably call NonTTY NoTTY (if I'm understanding the use correctly) as NoTTY is more consistent with naming in our other projects.

@caarlos0
Copy link
Copy Markdown
Contributor Author

caarlos0 commented Sep 30, 2025

@meowgorithm you can add a no tty error handler to example/main.go:

		fang.WithNoTTYErrorHandler(func(w io.Writer, err error) {
			fmt.Fprintf(w, "Custom no-TTY error: %v\n", err)
		}),

And then run it like:

go run ./example/main.go nopenopenope 2>&1 | cat

@caarlos0
Copy link
Copy Markdown
Contributor Author

that would be ideal, but would also require a breaking change, as currently the error handler expects the styles etc

if noTTY { fmt.Fprint(w, noStyles) }, no?

no... the err handler expects the styles.

@aymanbagabas
Copy link
Copy Markdown
Member

no... the err handler expects the styles.

Right, but can't we do something like this?

func DefaultErrorHandler(w io.Writer, styles Styles, err error) {
	if f, ok := w.(term.File); !ok || !term.IsTerminal(f.Fd()) {
		_, _ = fmt.Fprintln(w, err.Error())
		return
	}

	_, _ = fmt.Fprintln(w, styles.ErrorHeader.String())
	_, _ = fmt.Fprintln(w, styles.ErrorText.Render(err.Error()+"."))
	_, _ = fmt.Fprintln(w)
	if isUsageError(err) {
		_, _ = fmt.Fprintln(w, lipgloss.JoinHorizontal(
			lipgloss.Left,
			styles.ErrorText.UnsetWidth().Render("Try"),
			styles.Program.Flag.Render(" --help "),
			styles.ErrorText.UnsetWidth().UnsetMargins().UnsetTransform().Render("for usage."),
		))
		_, _ = fmt.Fprintln(w)
	}
}

@caarlos0
Copy link
Copy Markdown
Contributor Author

there's still the issue of that the user might want to do something different if output is not a tty (like we do by default in fang).
This way gives the most flexibility to the caller IMHO.

btw your suggestion is similar to my initial implementation, check PR description

@caarlos0
Copy link
Copy Markdown
Contributor Author

although, triple checking now, maybe the bg color thing isn't a problem as that check is also guarded by a if isTerm 🤔

@caarlos0
Copy link
Copy Markdown
Contributor Author

@aymanbagabas you are absolutely right!

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
@aymanbagabas
Copy link
Copy Markdown
Member

If we're breaking the API, I would query the terminal and check IsTTY for each FD, stdin, out, and err, then store them in the context and pass the context down to handlers etc. This way you don't need to perform the checks multiple times and you only query once.

@caarlos0
Copy link
Copy Markdown
Contributor Author

If we're breaking the API, I would query the terminal and check IsTTY for each FD, stdin, out, and err, then store them in the context and pass the context down to handlers etc. This way you don't need to perform the checks multiple times and you only query once.

not doing it, ended up going back to my og fix (which was also suggested by you), as it works well enough.

@caarlos0 caarlos0 merged commit 8764398 into main Sep 30, 2025
18 checks passed
@caarlos0 caarlos0 deleted the err-term branch September 30, 2025 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants