fix(docs): bound i18n codex prompt cleanup

This commit is contained in:
Vincent Koc
2026-04-29 17:47:02 -07:00
parent a548d8e1ea
commit 5a631e1ee9
4 changed files with 93 additions and 5 deletions

View File

@@ -0,0 +1,25 @@
//go:build !windows
package main
import (
"errors"
"os"
"os/exec"
"syscall"
)
func configureCodexPromptCommand(command *exec.Cmd) {
command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
command.Cancel = func() error {
if command.Process == nil {
return os.ErrProcessDone
}
err := syscall.Kill(-command.Process.Pid, syscall.SIGKILL)
if errors.Is(err, syscall.ESRCH) {
return os.ErrProcessDone
}
return err
}
command.WaitDelay = docsI18nCommandWaitDelay()
}

View File

@@ -0,0 +1,9 @@
//go:build windows
package main
import "os/exec"
func configureCodexPromptCommand(command *exec.Cmd) {
command.WaitDelay = docsI18nCommandWaitDelay()
}

View File

@@ -14,11 +14,13 @@ import (
)
const (
translateMaxAttempts = 3
translateBaseDelay = 15 * time.Second
defaultPromptTimeout = 2 * time.Minute
envDocsI18nPromptTimeout = "OPENCLAW_DOCS_I18N_PROMPT_TIMEOUT"
envDocsI18nCodexExecutable = "OPENCLAW_DOCS_I18N_CODEX_EXECUTABLE"
translateMaxAttempts = 3
translateBaseDelay = 15 * time.Second
defaultPromptTimeout = 2 * time.Minute
defaultCommandWaitDelay = 15 * time.Second
envDocsI18nPromptTimeout = "OPENCLAW_DOCS_I18N_PROMPT_TIMEOUT"
envDocsI18nCommandWaitDelay = "OPENCLAW_DOCS_I18N_COMMAND_WAIT_DELAY"
envDocsI18nCodexExecutable = "OPENCLAW_DOCS_I18N_CODEX_EXECUTABLE"
)
var errEmptyTranslation = errors.New("empty translation")
@@ -214,6 +216,7 @@ func runCodexExecPrompt(ctx context.Context, req codexPromptRequest) (string, er
"-",
}
command := exec.CommandContext(ctx, docsCodexExecutable(), args...)
configureCodexPromptCommand(command)
command.Stdin = strings.NewReader(buildCodexTranslationPrompt(req.SystemPrompt, req.Message))
command.Env = append(os.Environ(), "CODEX_HOME="+codexHome)
var stdout bytes.Buffer
@@ -327,3 +330,15 @@ func docsI18nPromptTimeout() time.Duration {
}
return parsed
}
func docsI18nCommandWaitDelay() time.Duration {
value := strings.TrimSpace(os.Getenv(envDocsI18nCommandWaitDelay))
if value == "" {
return defaultCommandWaitDelay
}
parsed, err := time.ParseDuration(value)
if err != nil || parsed <= 0 {
return defaultCommandWaitDelay
}
return parsed
}

View File

@@ -56,6 +56,14 @@ func TestDocsI18nPromptTimeoutUsesEnvOverride(t *testing.T) {
}
}
func TestDocsI18nCommandWaitDelayUsesEnvOverride(t *testing.T) {
t.Setenv(envDocsI18nCommandWaitDelay, "50ms")
if got := docsI18nCommandWaitDelay(); got != 50*time.Millisecond {
t.Fatalf("expected 50ms wait delay, got %s", got)
}
}
func TestIsRetryableTranslateErrorRejectsDeadlineExceeded(t *testing.T) {
t.Parallel()
@@ -235,6 +243,37 @@ printf 'translated from codex\n' > "$out"
}
}
func TestRunCodexExecPromptDoesNotHangOnInheritedPipesAfterTimeout(t *testing.T) {
dir := t.TempDir()
fakeCodex := filepath.Join(dir, "codex")
if err := os.WriteFile(fakeCodex, []byte(`#!/bin/sh
set -eu
(sleep 10) &
sleep 10
`), 0o755); err != nil {
t.Fatalf("write fake codex: %v", err)
}
t.Setenv(envDocsI18nCodexExecutable, fakeCodex)
t.Setenv(envDocsI18nCommandWaitDelay, "20ms")
t.Setenv("OPENAI_API_KEY", "test-openai-key")
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
started := time.Now()
_, err := runCodexExecPrompt(ctx, codexPromptRequest{
SystemPrompt: "Translate.",
Message: "Hello",
Model: "gpt-5.5",
Thinking: "high",
})
if err == nil {
t.Fatal("expected timeout error")
}
if elapsed := time.Since(started); elapsed > 2*time.Second {
t.Fatalf("expected bounded timeout, took %s", elapsed)
}
}
func TestPreviewCommandOutputFlattensAndTruncates(t *testing.T) {
input := "line one\n\nline two\tline three " + strings.Repeat("x", 600)
preview := previewCommandOutput(input, "")