diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-17 11:28:19 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-17 11:28:19 +0200 |
| commit | 6f1c8bf7a36eb7044ed7aad30f84664cbbf0d303 (patch) | |
| tree | dd2ac6e1433177fb59c167a12fa0b4b91132f34a /internal/hexaicli/run.go | |
| parent | 10562cc510f64d5ac38aeb76f03e18eb76cca40f (diff) | |
Fix bugs, remove duplication, and clean up code quality issues
- Log swallowed JSON unmarshal errors in stats and LSP handlers
- Fix debug log file handle leak in tmuxedit (return closer from initDebugLog)
- Check f.Close() errors on write paths in promptstore and tmuxedit
- Fix cacheGet TOCTOU race by using single write lock
- Fix readInput to use passed stdin reader instead of os.Stdin.Stat()
- Remove 45 'moved to' comment tombstones from lsp/handlers.go
- Deduplicate canonicalProvider wrappers (use llmutils.CanonicalProvider directly)
- Remove SetWindow side effect from stats.TakeSnapshot (pure read now)
- Move duplicated splitLines to textutil.SplitLinesBytes
- Collapse StatusSink.SetGlobal 10 params into GlobalStatus struct
- Simplify LRU touchLocked to in-place delete-and-append
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'internal/hexaicli/run.go')
| -rw-r--r-- | internal/hexaicli/run.go | 27 |
1 files changed, 21 insertions, 6 deletions
diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go index 806f824..9c7ba73 100644 --- a/internal/hexaicli/run.go +++ b/internal/hexaicli/run.go @@ -55,7 +55,7 @@ func buildCLIJobs(cfg appconfig.App) ([]cliJob, error) { if provider == "" { provider = cfg.Provider } - provider = canonicalProvider(provider) + provider = llmutils.CanonicalProvider(provider) derived := llmutils.ConfigForProvider(cfg, provider, entry.Model) req := buildCLIRequest(entry, provider, derived) jobs = append(jobs, cliJob{index: i, provider: provider, entry: entry, cfg: derived, req: req}) @@ -92,10 +92,6 @@ func cliTemperatureFromEntry(cfg appconfig.App, provider string, entry appconfig return llmutils.ResolveTemperature(provider, model, entry.Temperature, cfg.CodingTemperature) } -func canonicalProvider(name string) string { - return llmutils.CanonicalProvider(name) -} - // Run executes the Hexai CLI behavior given arguments and I/O streams. // It assumes flags have already been parsed by the caller. func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error { @@ -461,10 +457,29 @@ func filterJobsBySelection(jobs []cliJob, indices []int) ([]cliJob, error) { return filtered, nil } +// stater is the subset of *os.File needed to detect piped vs terminal input. +type stater interface { + Stat() (os.FileInfo, error) +} + +// isPipedInput reports whether stdin is a pipe or file (not a terminal). +// For *os.File it checks the file mode; for any other io.Reader it +// optimistically returns true since non-file readers are typically +// in-memory buffers used in tests. +func isPipedInput(stdin io.Reader) bool { + if f, ok := stdin.(stater); ok { + fi, err := f.Stat() + return err == nil && (fi.Mode()&os.ModeCharDevice) == 0 + } + // Non-file readers (e.g. strings.NewReader in tests) always have data. + return true +} + // readInput reads from stdin and args, then combines them per CLI rules. +// It uses the passed stdin reader (not os.Stdin) to detect piped input. func readInput(stdin io.Reader, args []string) (string, error) { var stdinData string - if fi, err := os.Stdin.Stat(); err == nil && (fi.Mode()&os.ModeCharDevice) == 0 { + if isPipedInput(stdin) { data, readErr := io.ReadAll(stdin) if readErr != nil { return "", fmt.Errorf("hexai: failed to read stdin: %w", readErr) |
