summaryrefslogtreecommitdiff
path: root/internal/hexaicli/run.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-17 11:28:19 +0200
committerPaul Buetow <paul@buetow.org>2026-03-17 11:28:19 +0200
commit6f1c8bf7a36eb7044ed7aad30f84664cbbf0d303 (patch)
treedd2ac6e1433177fb59c167a12fa0b4b91132f34a /internal/hexaicli/run.go
parent10562cc510f64d5ac38aeb76f03e18eb76cca40f (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.go27
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)