alibaba/open-code-review · error

%s timed out after %s: %w

Error message

%s timed out after %s: %w

What it means

resolveKeyCmd runs a user-configured shell command to fetch a credential under a fixed timeout. When the command's context expires with context.DeadlineExceeded, the error reports the timeout explicitly (wrapping ctx.Err() so errors.Is works) instead of surfacing the SIGKILL exit status the kill produces.

Source

Thrown at internal/llm/keycmd.go:89

	c.Stdin = os.Stdin
	// Buffer stdout through cappedBuffer rather than an *os.File so os/exec does
	// the copying in its own goroutine: that is what lets WaitDelay force the
	// pipe closed. exec.CommandContext SIGKILLs only the shell, so a grandchild
	// (gpg-agent, pinentry, `op`) that inherited the stdout pipe keeps it open
	// and Wait blocks on the read long past the timeout -- reproducible with
	// api_key_cmd = "sleep 200 & printf tok". WaitDelay makes Wait give up
	// shortly after the context dies.
	out := &cappedBuffer{max: keyCmdMaxOutput}
	c.Stdout = out
	c.WaitDelay = keyCmdWaitDelay

	err := c.Run()
	// Checked first so a timeout reports as such instead of as the SIGKILL exit
	// status it produces. (Run has already joined every stdout copier, so the
	// buffer below is safe to read on all paths.)
	if ctx.Err() == context.DeadlineExceeded {
		// Wrap ctx.Err() so callers can errors.Is(err, context.DeadlineExceeded).
		return "", fmt.Errorf("%s timed out after %s: %w", label, keyCmdTimeout, ctx.Err())
	}
	if out.overflow {
		return "", fmt.Errorf("%s produced more than 64KiB of output", label)
	}
	// ErrWaitDelay only means an orphaned grandchild still holds the pipe; the
	// command itself exited fine and its output is already buffered, so use it
	// rather than surfacing an exec-internal error.
	if err != nil && !errors.Is(err, exec.ErrWaitDelay) {
		// Covers non-zero exit and command-not-found (the shell exits non-zero
		// and prints its not-found message on the child's stderr). ExitError.Stderr
		// stays nil because we assigned c.Stderr, so no output can leak here.
		return "", fmt.Errorf("%s failed: %w", label, err)
	}

	// Trim a trailing line break; multi-line output past that is ambiguous and refused.
	// ContainsAny (not Contains "\n") so a lone interior CR is caught too: TrimRight
	// leaves it, TrimSpace below only strips the edges, and a CR inside a credential
	// makes net/http reject the Authorization header with an opaque error.

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Pre-authenticate the command (refresh AWS SSO/session, unlock the agent) so it returns quickly
  2. Run the command manually and time it; fix whatever hangs (prompt, network, wrong host)
  3. Check whether the command waits on stdin and remove that requirement
  4. If the command is legitimately slow, switch to a faster credential path — the loader timeout is fixed

Example fix

// before (hangs waiting for passphrase)
keyCmd: "pass show api-key"
// after (non-interactive source)
keyCmd: "cat ~/.secrets/api-key"
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-test helper latency and non-interactivity
if err := exec.Command("sh", "-c", "timeout 5 <your-key-cmd>").Run(); err != nil {
	// hangs or too slow; fix before relying on it
}

Type guard

func isTimeout(err error) bool { return errors.Is(err, context.DeadlineExceeded) }

Try / catch

key, err := resolveKeyCmd(ctx, cfg)
if errors.Is(err, context.DeadlineExceeded) {
	return fmt.Errorf("credential helper hangs or is too slow: %w", err)
}

Prevention

When it happens

Trigger: The key-retrieval command exceeds keyCmdTimeout — e.g. an aws/codeartifact CLI, a secrets-manager CLI, or an ssh-locked helper that hangs waiting for input or a slow network call.

Common situations: Expired AWS session making a CLI hang or retry slowly; a credential helper prompting interactively (passphrase) with no TTY; DNS/network stalls; wrong remote host in the command.

Understand the failure class

Related errors


AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02). Data as JSON: /api/errors/c4193e766515e2f6. Report an issue: GitHub.