{"record":{"id":"c4193e766515e2f6","repo":"alibaba/open-code-review","slug":"s-timed-out-after-s-w","errorCode":null,"errorMessage":"%s timed out after %s: %w","messagePattern":"(.+?) timed out after (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/llm/keycmd.go","lineNumber":89,"sourceCode":"\tc.Stdin = os.Stdin\n\t// Buffer stdout through cappedBuffer rather than an *os.File so os/exec does\n\t// the copying in its own goroutine: that is what lets WaitDelay force the\n\t// pipe closed. exec.CommandContext SIGKILLs only the shell, so a grandchild\n\t// (gpg-agent, pinentry, `op`) that inherited the stdout pipe keeps it open\n\t// and Wait blocks on the read long past the timeout -- reproducible with\n\t// api_key_cmd = \"sleep 200 & printf tok\". WaitDelay makes Wait give up\n\t// shortly after the context dies.\n\tout := &cappedBuffer{max: keyCmdMaxOutput}\n\tc.Stdout = out\n\tc.WaitDelay = keyCmdWaitDelay\n\n\terr := c.Run()\n\t// Checked first so a timeout reports as such instead of as the SIGKILL exit\n\t// status it produces. (Run has already joined every stdout copier, so the\n\t// buffer below is safe to read on all paths.)\n\tif ctx.Err() == context.DeadlineExceeded {\n\t\t// Wrap ctx.Err() so callers can errors.Is(err, context.DeadlineExceeded).\n\t\treturn \"\", fmt.Errorf(\"%s timed out after %s: %w\", label, keyCmdTimeout, ctx.Err())\n\t}\n\tif out.overflow {\n\t\treturn \"\", fmt.Errorf(\"%s produced more than 64KiB of output\", label)\n\t}\n\t// ErrWaitDelay only means an orphaned grandchild still holds the pipe; the\n\t// command itself exited fine and its output is already buffered, so use it\n\t// rather than surfacing an exec-internal error.\n\tif err != nil && !errors.Is(err, exec.ErrWaitDelay) {\n\t\t// Covers non-zero exit and command-not-found (the shell exits non-zero\n\t\t// and prints its not-found message on the child's stderr). ExitError.Stderr\n\t\t// stays nil because we assigned c.Stderr, so no output can leak here.\n\t\treturn \"\", fmt.Errorf(\"%s failed: %w\", label, err)\n\t}\n\n\t// Trim a trailing line break; multi-line output past that is ambiguous and refused.\n\t// ContainsAny (not Contains \"\\n\") so a lone interior CR is caught too: TrimRight\n\t// leaves it, TrimSpace below only strips the edges, and a CR inside a credential\n\t// makes net/http reject the Authorization header with an opaque error.","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/llm/keycmd.go#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pre-authenticate the command (refresh AWS SSO/session, unlock the agent) so it returns quickly","Run the command manually and time it; fix whatever hangs (prompt, network, wrong host)","Check whether the command waits on stdin and remove that requirement","If the command is legitimately slow, switch to a faster credential path — the loader timeout is fixed"],"exampleFix":"// before (hangs waiting for passphrase)\nkeyCmd: \"pass show api-key\"\n// after (non-interactive source)\nkeyCmd: \"cat ~/.secrets/api-key\"","handlingStrategy":"try-catch","validationCode":"// pre-test helper latency and non-interactivity\nif err := exec.Command(\"sh\", \"-c\", \"timeout 5 <your-key-cmd>\").Run(); err != nil {\n\t// hangs or too slow; fix before relying on it\n}","typeGuard":"func isTimeout(err error) bool { return errors.Is(err, context.DeadlineExceeded) }","tryCatchPattern":"key, err := resolveKeyCmd(ctx, cfg)\nif errors.Is(err, context.DeadlineExceeded) {\n\treturn fmt.Errorf(\"credential helper hangs or is too slow: %w\", err)\n}","preventionTips":["Ensure the helper never prompts interactively (no TTY in automation)","Pre-authenticate upstream sessions (AWS SSO, gpg-agent) before running","Test the command under `timeout` to catch hangs early"],"tags":["timeout","exec","credentials","context"],"backgroundTag":"command-timeout","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}