]> git.giacomolli.uk - onechat/commitdiff
Check operation context on returned errors
authorAnderson A. Giacomolli <redacted>
Mon, 9 Nov 2026 21:00:39 +0000 (18:00 -0300)
committerAnderson A. Giacomolli <redacted>
Mon, 9 Nov 2026 21:28:42 +0000 (18:28 -0300)
Wrapcheck requires operation context on errors from package and
interface calls. Returned errors keep their causes for errors.Is and
errors.As. Context cancellation and the listed functions with one error
path keep their direct returns, as the Go rules allow.

12 files changed:
.golangci.yml
chain.go
chatgpt.go
log.go
main.go
subagent_run_test.go
terminal.go
terminal_client_helper_test.go
tools.go
tools_test.go
tree.go
view.go

index 58a78b4c823a256468182eee56ffd0ce06e993a0..bba6f25a3bcaf1190b09895e04be4d5af7fa99af 100644 (file)
@@ -7,3 +7,30 @@ linters:
     - gocritic
     - testifylint
     - revive
     - gocritic
     - testifylint
     - revive
+    - wrapcheck
+  settings:
+    wrapcheck:
+      # go-code: Return ctx.Err() directly.
+      extra-ignore-sigs:
+        - "(context.Context).Err() error"
+  exclusions:
+    rules:
+      # go-code: A function with one error path can return it directly.
+      - linters: [wrapcheck]
+        path: ^records\.go$
+        source: 'return r\.file\.Close\(\)'
+      - linters: [wrapcheck]
+        path: ^log_test\.go$
+        source: 'return os\.Truncate\('
+      - linters: [wrapcheck]
+        path: ^sync_buffer_helper_test\.go$
+        source: 'return b\.buffer\.Write\('
+      - linters: [wrapcheck]
+        path: ^probe\.go$
+        source: 'return s\.inner\.Summarize\('
+      - linters: [wrapcheck]
+        path: ^view\.go$
+        source: 'return false, err'
+      - linters: [wrapcheck]
+        path: ^whatsapp\.go$
+        text: 'sqlstore\.Container\)\.Close\('
index 3ab9fe114e7d4a3d09c459bc7be534ec89d7943c..0211787132ad6c940cfeed0005d2a06c264da3fa 100644 (file)
--- a/chain.go
+++ b/chain.go
@@ -43,7 +43,7 @@ func (c *Chain) Run(ctx context.Context, input []any) (string, error) {
                request.Input = input
                response, err := c.client.Create(ctx, request)
                if err != nil {
                request.Input = input
                response, err := c.client.Create(ctx, request)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("create model response: %w", err)
                }
 
                var text string
                }
 
                var text string
index 302e143bb732d029378ce5382f9e519eb8753c8f..7a5afc20865b4b547af24fa73449d0ed506e816b 100644 (file)
@@ -145,7 +145,7 @@ func (a *ChatGPTAuth) Login(ctx context.Context, out io.Writer) error {
 
        _, err = fmt.Fprintf(out, "Open %s/codex/device on any device and enter this code:\n\n    %s\n\nWaiting for the approval...\n", a.issuer, userCode)
        if err != nil {
 
        _, err = fmt.Fprintf(out, "Open %s/codex/device on any device and enter this code:\n\n    %s\n\nWaiting for the approval...\n", a.issuer, userCode)
        if err != nil {
-               return err
+               return fmt.Errorf("write device code: %w", err)
        }
 
        grant, err := a.poll(ctx, code.DeviceAuthID, userCode, chatgptPollInterval(code.Interval))
        }
 
        grant, err := a.poll(ctx, code.DeviceAuthID, userCode, chatgptPollInterval(code.Interval))
@@ -164,8 +164,11 @@ func (a *ChatGPTAuth) Login(ctx context.Context, out io.Writer) error {
        }
 
        _, err = fmt.Fprintln(out, "Logged in.")
        }
 
        _, err = fmt.Fprintln(out, "Logged in.")
+       if err != nil {
+               return fmt.Errorf("write login result: %w", err)
+       }
 
 
-       return err
+       return nil
 }
 
 // read reads the token file.
 }
 
 // read reads the token file.
diff --git a/log.go b/log.go
index 24feb3bb21ec63acf0187420a23c2723f821ecdb..75080f9912c7bd7479d125689372b2a959c371ea 100644 (file)
--- a/log.go
+++ b/log.go
@@ -208,7 +208,7 @@ func (l *Log) catchUp(ctx context.Context) error {
                var message Message
                err := json.Unmarshal(line, &message)
                if err != nil {
                var message Message
                err := json.Unmarshal(line, &message)
                if err != nil {
-                       return err
+                       return fmt.Errorf("decode log message: %w", err)
                }
 
                if message.ID != l.index.count {
                }
 
                if message.ID != l.index.count {
diff --git a/main.go b/main.go
index ac4afe48d19185265c0053741acb4442a2734aa6..e25e7f383e7f3013e00e4837deac79e731b486be 100644 (file)
--- a/main.go
+++ b/main.go
@@ -42,7 +42,7 @@ func run() error {
        case len(args) == 1 && (args[0] == "help" || args[0] == "--help" || args[0] == "-h"):
                _, err := fmt.Print(usage)
                if err != nil {
        case len(args) == 1 && (args[0] == "help" || args[0] == "--help" || args[0] == "-h"):
                _, err := fmt.Print(usage)
                if err != nil {
-                       return err
+                       return fmt.Errorf("write command help: %w", err)
                }
        case len(args) == 1 && args[0] == "login":
                err := chatgptLogin(ctx, os.Stdout)
                }
        case len(args) == 1 && args[0] == "login":
                err := chatgptLogin(ctx, os.Stdout)
index 0341ba6dde6e8fd77deb1f3ca48ae44c2855c140..b76c18d8bab5cddedf41ae7074ca627cf6888cc8 100644 (file)
@@ -185,11 +185,11 @@ func TestFullContextGivesAFailedReport(t *testing.T) {
 
        name := test.events.Last("subagent")
        require.Equal(t, []string{
 
        name := test.events.Last("subagent")
        require.Equal(t, []string{
-               "work: [" + name + "]\nfailed: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
+               "work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
                "reply: It failed.",
        }, test.Log(t))
        require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
                "reply: It failed.",
        }, test.Log(t))
        require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
-       require.Equal(t, "subagent "+name+" started\nsubagent "+name+" failed after 0s: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
+       require.Equal(t, "subagent "+name+" started\nsubagent "+name+" failed after 0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
 }
 
 func TestSubagentHasItsOwnEffortAndNoSpawn(t *testing.T) {
 }
 
 func TestSubagentHasItsOwnEffortAndNoSpawn(t *testing.T) {
index 876986a4479f38a679d9d2dc586be8a85164ac15..eff009cd9f7e225f45e96b72f8e72e3f79f20eca 100644 (file)
@@ -71,7 +71,11 @@ func terminalSession(ctx context.Context, conn net.Conn, in io.Reader, out io.Wr
                <-shown
        }
 
                <-shown
        }
 
-       return err
+       if err != nil {
+               return fmt.Errorf("run terminal client: %w", err)
+       }
+
+       return nil
 }
 
 // terminalSendLines sends each line of in as a request until the input ends.
 }
 
 // terminalSendLines sends each line of in as a request until the input ends.
@@ -113,7 +117,12 @@ func terminalSendLines(ctx context.Context, conn net.Conn, in io.Reader) error {
                }
        }
 
                }
        }
 
-       return scanner.Err()
+       err := scanner.Err()
+       if err != nil {
+               return fmt.Errorf("read terminal input: %w", err)
+       }
+
+       return nil
 }
 
 // terminalShowEvents writes each event from the daemon to out until the
 }
 
 // terminalShowEvents writes each event from the daemon to out until the
@@ -135,11 +144,16 @@ func terminalShowEvents(conn net.Conn, out io.Writer) error {
 
                _, err = io.WriteString(out, text+"\n")
                if err != nil {
 
                _, err = io.WriteString(out, text+"\n")
                if err != nil {
-                       return err
+                       return fmt.Errorf("write terminal event: %w", err)
                }
        }
 
                }
        }
 
-       return scanner.Err()
+       err := scanner.Err()
+       if err != nil {
+               return fmt.Errorf("read daemon events: %w", err)
+       }
+
+       return nil
 }
 
 // terminalCmpError returns err, or fallback when err is nil.
 }
 
 // terminalCmpError returns err, or fallback when err is nil.
index 077d90c6a282def0fd0c4f518a424e8a15af4851..0fdd93bee91b8b7b9e4fc86e776dcfcc2a632064 100644 (file)
@@ -1,6 +1,7 @@
 package main
 
 import (
 package main
 
 import (
+       "fmt"
        "io"
        "net"
        "strings"
        "io"
        "net"
        "strings"
@@ -44,7 +45,7 @@ func (c *TerminalTestClient) WaitFor(t *testing.T, text string) {
 func (c *TerminalTestClient) Close() error {
        err := c.input.Close()
        if err != nil {
 func (c *TerminalTestClient) Close() error {
        err := c.input.Close()
        if err != nil {
-               return err
+               return fmt.Errorf("close terminal input: %w", err)
        }
 
        return <-c.done
        }
 
        return <-c.done
index 009e3312404c646f53a8fea4284371264b420a70..1cb098a564a0d968d6b447bba55f8c1b11defab7 100644 (file)
--- a/tools.go
+++ b/tools.go
@@ -71,12 +71,12 @@ type Toolbox struct {
 func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, error) {
        _, err := b.log.Append("tool", toolCallText(name, arguments))
        if err != nil {
 func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, error) {
        _, err := b.log.Append("tool", toolCallText(name, arguments))
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("append tool call: %w", err)
        }
 
        err = b.log.Flush(ctx)
        if err != nil && ctx.Err() == nil {
        }
 
        err = b.log.Flush(ctx)
        if err != nil && ctx.Err() == nil {
-               return "", err
+               return "", fmt.Errorf("flush tool call: %w", err)
        }
 
        output := "not run: the turn was interrupted"
        }
 
        output := "not run: the turn was interrupted"
@@ -86,7 +86,7 @@ func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, err
 
        _, err = b.log.Append("echo", output)
        if err != nil {
 
        _, err = b.log.Append("echo", output)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("append tool result: %w", err)
        }
 
        return output, nil
        }
 
        return output, nil
@@ -234,7 +234,7 @@ func (b *Toolbox) zoom(id, n int) (string, error) {
        if n == 1 {
                message, err := b.chat.Message(id)
                if err != nil {
        if n == 1 {
                message, err := b.chat.Message(id)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("read zoom message: %w", err)
                }
 
                return leafSource(message), nil
                }
 
                return leafSource(message), nil
@@ -245,7 +245,7 @@ func (b *Toolbox) zoom(id, n int) (string, error) {
        for _, index := range []int{2 * id / n, 2*id/n + 1} {
                node, built, err := b.chat.Node(level, index)
                if err != nil {
        for _, index := range []int{2 * id / n, 2*id/n + 1} {
                node, built, err := b.chat.Node(level, index)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("read zoom node: %w", err)
                }
 
                text := viewPending
                }
 
                text := viewPending
@@ -267,12 +267,12 @@ func (b *Toolbox) date(id int) (string, error) {
 
        message, err := b.chat.Message(id)
        if err != nil {
 
        message, err := b.chat.Message(id)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read dated message: %w", err)
        }
 
        date, err := time.Parse(time.RFC3339Nano, message.Date)
        if err != nil {
        }
 
        date, err := time.Parse(time.RFC3339Nano, message.Date)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("parse message date: %w", err)
        }
 
        return date.Local().Format("Monday 2006-01-02 15:04:05 -07:00"), nil
        }
 
        return date.Local().Format("Monday 2006-01-02 15:04:05 -07:00"), nil
@@ -317,7 +317,7 @@ func (b *Toolbox) shell(ctx context.Context, command string) string {
 func (b *Toolbox) read(path string) (text string, err error) {
        file, err := os.Open(b.path(path))
        if err != nil {
 func (b *Toolbox) read(path string) (text string, err error) {
        file, err := os.Open(b.path(path))
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("open tool file: %w", err)
        }
 
        defer func() {
        }
 
        defer func() {
@@ -327,7 +327,7 @@ func (b *Toolbox) read(path string) (text string, err error) {
        output := &ToolHeadTail{}
        _, err = io.Copy(output, file)
        if err != nil {
        output := &ToolHeadTail{}
        _, err = io.Copy(output, file)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read tool file: %w", err)
        }
 
        return output.String(), nil
        }
 
        return output.String(), nil
@@ -339,12 +339,12 @@ func (b *Toolbox) write(path, text string) (string, error) {
        path = b.path(path)
        err := os.MkdirAll(filepath.Dir(path), 0o755)
        if err != nil {
        path = b.path(path)
        err := os.MkdirAll(filepath.Dir(path), 0o755)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("create tool file directory: %w", err)
        }
 
        err = os.WriteFile(path, []byte(text), 0o644)
        if err != nil {
        }
 
        err = os.WriteFile(path, []byte(text), 0o644)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("write tool file: %w", err)
        }
 
        return fmt.Sprintf("wrote %d bytes to %s", len(text), path), nil
        }
 
        return fmt.Sprintf("wrote %d bytes to %s", len(text), path), nil
@@ -356,7 +356,7 @@ func (b *Toolbox) edit(path, old, replacement string) (string, error) {
        path = b.path(path)
        data, err := os.ReadFile(path)
        if err != nil {
        path = b.path(path)
        data, err := os.ReadFile(path)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read file to edit: %w", err)
        }
 
        if old == "" {
        }
 
        if old == "" {
@@ -370,12 +370,12 @@ func (b *Toolbox) edit(path, old, replacement string) (string, error) {
 
        info, err := os.Stat(path)
        if err != nil {
 
        info, err := os.Stat(path)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read file mode: %w", err)
        }
 
        err = os.WriteFile(path, []byte(strings.Replace(string(data), old, replacement, 1)), info.Mode().Perm())
        if err != nil {
        }
 
        err = os.WriteFile(path, []byte(strings.Replace(string(data), old, replacement, 1)), info.Mode().Perm())
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("write edited file: %w", err)
        }
 
        return "edited " + path, nil
        }
 
        return "edited " + path, nil
index 45cd4a242f1ee6024406c6d56a4c918d8fc14998..ed584387a10e5517f3e61009dcc20313bb800c6f 100644 (file)
@@ -107,7 +107,7 @@ func TestFileTools(t *testing.T) {
        require.Equal(t, "error: old occurs 2 times in "+path+", not once", box.run(t.Context(), "edit", `{"path":"notes/a.txt","old":"two","new":"2"}`))
        require.Equal(t, "edited "+path, box.run(t.Context(), "edit", `{"path":"notes/a.txt","old":"one","new":"1"}`))
        require.Equal(t, "1 two two\n", box.run(t.Context(), "read", `{"path":"notes/a.txt"}`))
        require.Equal(t, "error: old occurs 2 times in "+path+", not once", box.run(t.Context(), "edit", `{"path":"notes/a.txt","old":"two","new":"2"}`))
        require.Equal(t, "edited "+path, box.run(t.Context(), "edit", `{"path":"notes/a.txt","old":"one","new":"1"}`))
        require.Equal(t, "1 two two\n", box.run(t.Context(), "read", `{"path":"notes/a.txt"}`))
-       require.Contains(t, box.run(t.Context(), "read", `{"path":"missing.txt"}`), "error: open "+filepath.Join(dir, "missing.txt"))
+       require.Contains(t, box.run(t.Context(), "read", `{"path":"missing.txt"}`), "error: open tool file: open "+filepath.Join(dir, "missing.txt"))
        require.Equal(t, `error: arguments: json: unknown field "file"`, box.run(t.Context(), "read", `{"file":"a.txt"}`))
        require.Equal(t, `error: unknown tool "grep"`, box.run(t.Context(), "grep", `{}`))
 }
        require.Equal(t, `error: arguments: json: unknown field "file"`, box.run(t.Context(), "read", `{"file":"a.txt"}`))
        require.Equal(t, `error: unknown tool "grep"`, box.run(t.Context(), "grep", `{}`))
 }
diff --git a/tree.go b/tree.go
index af164aed859ac7f64784c60d4c301314aab27b7f..83ecda3d5a8c4885c100591852ec2c30c46e733e 100644 (file)
--- a/tree.go
+++ b/tree.go
@@ -247,7 +247,7 @@ func (t *Tree) open(ctx context.Context, total int) error {
                var node Node
                err := json.Unmarshal(line, &node)
                if err != nil {
                var node Node
                err := json.Unmarshal(line, &node)
                if err != nil {
-                       return err
+                       return fmt.Errorf("decode tree node: %w", err)
                }
 
                err = t.check(node)
                }
 
                err = t.check(node)
diff --git a/view.go b/view.go
index 233e08921f7a1ec2699244832177be784c3faae4..2124d3d30ed4b23d801afadbfecba2c23aabf453 100644 (file)
--- a/view.go
+++ b/view.go
@@ -316,7 +316,7 @@ func (v *View) mostDue(tree ViewNodeReader) (int, error) {
 
                built, err := tree.Built(left.level+1, left.index/2)
                if err != nil {
 
                built, err := tree.Built(left.level+1, left.index/2)
                if err != nil {
-                       return -1, err
+                       return -1, fmt.Errorf("check parent node: %w", err)
                }
 
                if built {
                }
 
                if built {