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:
- 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\('
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 "", fmt.Errorf("create model response: %w", err)
_, 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 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))
}
_, err = fmt.Fprintln(out, "Logged in.")
}
_, err = fmt.Fprintln(out, "Logged in.")
+ if err != nil {
+ return fmt.Errorf("write login result: %w", err)
+ }
}
// read reads the token file.
}
// read reads the token file.
var message Message
err := json.Unmarshal(line, &message)
if err != nil {
var message Message
err := json.Unmarshal(line, &message)
if err != nil {
+ return fmt.Errorf("decode log message: %w", err)
}
if message.ID != l.index.count {
}
if message.ID != l.index.count {
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 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)
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) {
+ 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.
+ 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
_, err = io.WriteString(out, text+"\n")
if err != nil {
_, err = io.WriteString(out, text+"\n")
if err != nil {
+ return fmt.Errorf("write terminal event: %w", 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.
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 fmt.Errorf("close terminal input: %w", err)
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 "", 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 "", fmt.Errorf("flush tool call: %w", err)
}
output := "not run: the turn was interrupted"
}
output := "not run: the turn was interrupted"
_, err = b.log.Append("echo", output)
if err != nil {
_, err = b.log.Append("echo", output)
if err != nil {
+ return "", fmt.Errorf("append tool result: %w", err)
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 "", fmt.Errorf("read zoom message: %w", err)
}
return leafSource(message), nil
}
return leafSource(message), nil
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 "", fmt.Errorf("read zoom node: %w", err)
message, err := b.chat.Message(id)
if err != nil {
message, err := b.chat.Message(id)
if err != nil {
+ 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 "", 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
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 "", fmt.Errorf("open tool file: %w", err)
output := &ToolHeadTail{}
_, err = io.Copy(output, file)
if err != nil {
output := &ToolHeadTail{}
_, err = io.Copy(output, file)
if err != nil {
+ return "", fmt.Errorf("read tool file: %w", err)
}
return output.String(), nil
}
return output.String(), nil
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 "", 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 "", 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
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 "", fmt.Errorf("read file to edit: %w", err)
info, err := os.Stat(path)
if err != nil {
info, err := os.Stat(path)
if err != nil {
+ 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 "", fmt.Errorf("write edited file: %w", err)
}
return "edited " + path, nil
}
return "edited " + path, nil
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", `{}`))
}
var node Node
err := json.Unmarshal(line, &node)
if err != nil {
var node Node
err := json.Unmarshal(line, &node)
if err != nil {
+ return fmt.Errorf("decode tree node: %w", err)
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, fmt.Errorf("check parent node: %w", err)