cli/cli · warning

file contains terminal escape sequences; use --allow-escape-

Error message

file contains terminal escape sequences; use --allow-escape-sequences to read anyway

What it means

Terminal-safety guard in `gh repo read-file`: before writing fetched file content to output, the bytes are scanned with iostreams.ContainsEscapeSequence, and if any terminal escape sequences are present the write is refused unless --allow-escape-sequences was passed. Unlike the release-download guard this applies in BOTH TTY and non-TTY modes, because even piped output typically ends up in some downstream terminal, and a malicious repo file could otherwise inject ANSI/OSC sequences.

Source

Thrown at pkg/cmd/repo/read-file/read_file.go:199

	// read-file does its own escape-sequence guarding below, so it writes raw
	// bytes through ContentOut in passthrough mode. Leaving sanitization on
	// would corrupt binary files and strip the escapes that
	// --allow-escape-sequences explicitly allows.
	opts.IO.SetContentSanitization(false)

	if mime, ok := iostreams.BinaryContentType(file.Content); ok {
		if opts.IO.IsStdoutTTY() {
			return fmt.Errorf("binary file (%s, %s); use --output to save to a file or pipe stdout",
				mime, text.FormatSize(int64(file.Size)))
		}
		_, err = opts.IO.ContentOut.Write(file.Content)
		return err
	}

	// Refuse terminal escape sequences unless --allow-escape-sequences, in both TTY and non-TTY modes,
	// so a malicious file cannot manipulate a downstream terminal.
	if !opts.AllowEscapeSequences && iostreams.ContainsEscapeSequence(file.Content) {
		return errors.New("file contains terminal escape sequences; use --allow-escape-sequences to read anyway")
	}

	if opts.IO.IsStdoutTTY() {
		if err := opts.IO.StartPager(); err != nil {
			fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err)
		}
		defer opts.IO.StopPager()
	}

	_, err = opts.IO.ContentOut.Write(file.Content)
	return err
}

// loadContent fetches the raw file bytes when the Contents API did not return them inline.
// The API only omits inline content for large files, which it marks with a "none" encoding;
// everything else (including empty files) comes back base64-encoded, so there is nothing to fetch.
func loadContent(httpClient *http.Client, repo ghrepo.Interface, file *repoFile, ref string) error {
	if file.Encoding != "none" {

View on GitHub (pinned to 0eeec0b92e)

Solutions

  1. If you trust the file, opt in: gh repo read-file owner/repo/FILE --allow-escape-sequences
  2. Otherwise save to a file instead of stdout with --output, then inspect with a tool that renders escapes safely
  3. Scan the file content for 0x1b bytes first if provenance is unknown

Example fix

# before
gh repo read-file org/repo/build.log
# after
gh repo read-file org/repo/build.log --allow-escape-sequences
Defensive patterns

Strategy: validation

Validate before calling

content := fetchFile(owner, repo, path)
if !opts.AllowEscapeSequences && iostreams.ContainsEscapeSequence(content) {
    return errors.New("file contains escape sequences; pass --allow-escape-sequences or --output FILE")
}

Try / catch

if err != nil && strings.Contains(err.Error(), "terminal escape sequences") {
    if trusted {
        err = rerunWithFlag("--allow-escape-sequences")
    } else {
        err = rerunWithOutput(tmpFile) // inspect safely
    }
}

Prevention

When it happens

Trigger: Reading a file from a repository whose bytes contain ESC (0x1b) followed by [ or ] sequences: files with embedded ANSI colors, crafted malicious repo content, or binary-ish text files that happen to contain 0x1b. Applies to both `gh repo read-file owner/repo/file` and programmatic use.

Common situations: Reading a repo-hosted .log, .txt, or dotfile with color codes; CI that cats repo files through gh; security-conscious teams scanning untrusted repos. The comment in the source explicitly states the refusal covers non-TTY mode too.

Related errors


AI-assisted analysis of cli/cli@0eeec0b92e (2026-08-15). Data as JSON: /api/errors/4fa85335a8da1d13. Report an issue: GitHub.