]> git.giacomolli.uk - onechat/commitdiff
Add local context to chat operation failures
authorAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:49:23 +0000 (11:49 -0300)
committerAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:49:23 +0000 (11:49 -0300)
Daemon, chat, schedule, and worker failures identify their local
operation while wrapping the original cause. Successful results keep a
nil error. Privacy-safe causes and direct context errors retain their
existing handling, and shutdown and retry decisions do not change.

Issue: 3cdc678

17 files changed:
chatgpt.go
command.go
daemon.go
daemon_test.go
schedule.go
schedule_cron.go
schedule_cron_test.go
subagent_run.go
subagent_run_test.go
telegram.go
terminal.go
tools.go
tools_test.go
whatsapp.go
whatsapp_inbox.go
whatsapp_send.go
whatsapp_send_test.go

index 77481905822674f27c9bc4bd888933d046514c51..49be0150264f31dfdabc28b4c0626a54ac71f8e3 100644 (file)
@@ -116,14 +116,14 @@ func (a *ChatGPTAuth) Authorize(ctx context.Context, header http.Header) error {
 
        tokens, err := a.read()
        if err != nil {
-               return err
+               return fmt.Errorf("read login tokens: %w", err)
        }
 
        expiry, found := chatgptTokenExpiry(tokens.AccessToken)
        if !found || !time.Now().Before(expiry.Add(-chatgptRefreshMargin)) {
                tokens, err = a.refresh(ctx, tokens)
                if err != nil {
-                       return err
+                       return fmt.Errorf("refresh login tokens: %w", err)
                }
        }
 
@@ -184,7 +184,7 @@ func (a *ChatGPTAuth) refresh(ctx context.Context, tokens ChatGPTTokenFile) (Cha
 
        err = a.write(tokens)
        if err != nil {
-               return ChatGPTTokenFile{}, err
+               return ChatGPTTokenFile{}, fmt.Errorf("save refreshed tokens: %w", err)
        }
 
        return tokens, nil
@@ -199,8 +199,11 @@ func (a *ChatGPTAuth) postJSON(ctx context.Context, path string, body, result an
        }
 
        _, err = a.post(ctx, path, "application/json", data, result)
+       if err != nil {
+               return fmt.Errorf("post JSON request: %w", err)
+       }
 
-       return err
+       return nil
 }
 
 // write replaces the token file atomically. The file has mode 0600.
@@ -278,17 +281,17 @@ func (a *ChatGPTAuth) Login(ctx context.Context, out io.Writer) error {
 
        grant, err := a.poll(ctx, code.DeviceAuthID, userCode, chatgptPollInterval(code.Interval))
        if err != nil {
-               return err
+               return fmt.Errorf("wait for device grant: %w", err)
        }
 
        tokens, err := a.exchange(ctx, grant)
        if err != nil {
-               return err
+               return fmt.Errorf("exchange device grant: %w", err)
        }
 
        err = a.write(tokens)
        if err != nil {
-               return err
+               return fmt.Errorf("save login tokens: %w", err)
        }
 
        _, err = fmt.Fprintln(out, "Logged in.")
@@ -443,12 +446,12 @@ func chatgptDecodeClaims(token string, claims any) bool {
 func chatgptLogin(ctx context.Context, out io.Writer) (err error) {
        dir, err := dataDir()
        if err != nil {
-               return err
+               return fmt.Errorf("find data directory: %w", err)
        }
 
        tokens, err := lockTokens(dir)
        if err != nil {
-               return err
+               return fmt.Errorf("lock login tokens: %w", err)
        }
 
        defer func() {
index a49f1d9c007b6ef5f1739708cab70d1986d2b95a..02901e2a8f93a3df841e4fbc23101a38abd15420 100644 (file)
@@ -48,7 +48,7 @@ func parseCommand(ctx context.Context, args []string) error {
        case len(args) == 1 && args[0] == "login":
                err := chatgptLogin(ctx, os.Stdout)
                if err != nil {
-                       return err
+                       return fmt.Errorf("log in: %w", err)
                }
 
        // A signal is the normal stop of the daemon and the client, so their
@@ -66,7 +66,7 @@ func parseCommand(ctx context.Context, args []string) error {
        case len(args) > 0 && args[0] == "probe":
                err := probe(ctx, args[1:], os.Stdout)
                if err != nil {
-                       return err
+                       return fmt.Errorf("run probe: %w", err)
                }
        default:
                return fmt.Errorf("unknown command %q\n\n%s", strings.Join(args, " "), strings.TrimSuffix(usage, "\n"))
index d6aa6aaa8cf247cb2ec4a613fab3344cf0b7eaaf..962070a11e7f38b84709173e96b3c17395b13a6c 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -213,8 +213,11 @@ func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, an
                }
 
                _, err := l.Submit(terminalSource, request.Text)
+               if err != nil {
+                       return fmt.Errorf("submit terminal message: %w", err)
+               }
 
-               return err
+               return nil
        case "image":
                if !filepath.IsAbs(request.Path) {
                        return errors.New("use /image PATH [caption]")
@@ -252,7 +255,11 @@ func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, an
                        err = fmt.Errorf("no subagent %s runs", request.Text)
                }
 
-               return err
+               if err != nil {
+                       return fmt.Errorf("stop subagent: %w", err)
+               }
+
+               return nil
        default:
                return fmt.Errorf("unknown request %q", request.Type)
        }
@@ -268,7 +275,7 @@ func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, an
 func daemon(ctx context.Context, out io.Writer) (err error) {
        dir, err := dataDir()
        if err != nil {
-               return err
+               return fmt.Errorf("find data directory: %w", err)
        }
 
        // Check the socket path before the daemon writes a file, so that a data
@@ -276,22 +283,22 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
        socket := filepath.Join(dir, socketFileName)
        err = checkSocketPath(socket)
        if err != nil {
-               return err
+               return fmt.Errorf("check daemon socket path: %w", err)
        }
 
        config, err := loadConfig(dir)
        if err != nil {
-               return err
+               return fmt.Errorf("load daemon config: %w", err)
        }
 
        botSettings, hasBot, err := loadTelegramConfig(dir)
        if err != nil {
-               return err
+               return fmt.Errorf("load Telegram config: %w", err)
        }
 
        tokens, err := lockTokens(dir)
        if err != nil {
-               return err
+               return fmt.Errorf("lock login tokens: %w", err)
        }
 
        defer func() {
@@ -303,7 +310,7 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
        logger := daemonLogger(out, "onechat: ")
        whatsapp, err := openWhatsApp(ctx, dir, time.Now, daemonLogger(out, "whatsapp: "), whatsappNewClient)
        if err != nil {
-               return err
+               return fmt.Errorf("open WhatsApp: %w", err)
        }
 
        defer func() {
@@ -312,12 +319,12 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
 
        address, auth, err := modelEndpoint(config, dir)
        if err != nil {
-               return err
+               return fmt.Errorf("configure model endpoint: %w", err)
        }
 
        agents, err := openDaemonAgents(ctx, dir, address, auth, config.Compactor, time.Now, logger)
        if err != nil {
-               return err
+               return fmt.Errorf("open daemon agents: %w", err)
        }
 
        defer func() {
@@ -332,7 +339,7 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
 
        listener, err := daemonListenSocket(socket)
        if err != nil {
-               return err
+               return fmt.Errorf("listen for daemon clients: %w", err)
        }
 
        linked := "not linked"
@@ -363,7 +370,7 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
 func daemonListenSocket(path string) (net.Listener, error) {
        err := checkSocketPath(path)
        if err != nil {
-               return nil, err
+               return nil, fmt.Errorf("check daemon socket path: %w", err)
        }
 
        err = os.Remove(path)
index 6a4d98c5a040456d0baf8e06fb4307542bcbb56a..c6cc2279a3435c514de8f943028fbc74f6cb8825 100644 (file)
@@ -39,7 +39,7 @@ func TestClientsSeeTheTurnAsItHappens(t *testing.T) {
        first.Type(stopCommand)
        first.WaitFor(t, "[error] no turn runs")
        first.Type(stopCommand + " BlueFox")
-       first.WaitFor(t, "[error] no subagent BlueFox runs")
+       first.WaitFor(t, "[error] stop subagent: no subagent BlueFox runs")
        require.NotContains(t, second.Output(), "no turn runs")
        require.NoError(t, first.Close())
 }
index 7eb747ff6d75205cf9a7b8c11b0ee4bb45dec370..23462d292e634b70906646d88def5ea7712abef8 100644 (file)
@@ -225,7 +225,7 @@ func (s *Schedules) deliver(ctx context.Context, l *TurnLoop) error {
 
                err = s.Done(schedule, now)
                if err != nil {
-                       return err
+                       return fmt.Errorf("acknowledge schedule run: %w", err)
                }
 
                attrs := []any{"id", schedule.ID}
@@ -352,7 +352,7 @@ func (s *Schedules) Set(text string, at, cron *string) (string, error) {
        default:
                parsed, err := scheduleParseCron(*cron)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("parse schedule expression: %w", err)
                }
 
                next, found := parsed.Next(now)
@@ -372,7 +372,7 @@ func (s *Schedules) Set(text string, at, cron *string) (string, error) {
        s.file.Schedules = append(s.file.Schedules, schedule)
        err := s.save()
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("save new schedule: %w", err)
        }
 
        s.change()
@@ -430,7 +430,7 @@ func (s *Schedules) Remove(id int) (string, error) {
        s.file.Schedules = slices.Delete(s.file.Schedules, index, index+1)
        err := s.save()
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("save removed schedule: %w", err)
        }
 
        s.change()
index b982f37921e03917bc76f9834459c373c3e2d77a..bf1424f9a6ad5e9b53cd9023cf7d87a33525c3e1 100644 (file)
@@ -277,7 +277,7 @@ func scheduleCronField(field string, low, high int) (uint64, error) {
 
                        first, err = scheduleCronNumber(base, low, high)
                        if err != nil {
-                               return 0, err
+                               return 0, fmt.Errorf("parse cron field value: %w", err)
                        }
 
                        last = first
index 06d23cf4b62bdea0bc73cf96c67f774b87848542..33d861b6e17531c1bddc16a798af224a59eb5631 100644 (file)
@@ -72,7 +72,7 @@ func TestScheduleParseCronRefuses(t *testing.T) {
                {
                        name:       "rejects a day name",
                        expression: "0 8 * * MON",
-                       message:    `the day of week field of "0 8 * * MON": "MON" is not a number from 0 to 7`,
+                       message:    `the day of week field of "0 8 * * MON": parse cron field value: "MON" is not a number from 0 to 7`,
                },
                {
                        name:       "rejects a month name",
index 24e4f8229c975a3f9fca2e7e3002530e7da76a99..e96975c90abd0cec2a59519b2bbcab18811ebcd4 100644 (file)
@@ -132,7 +132,7 @@ func (r *SubagentRuns) Spawn(ctx context.Context, tasks []string, effort *string
 
        view, err := r.layout.Split(r.memory.ViewLines(nil))
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("split worker view: %w", err)
        }
 
        names := make([]string, 0, len(tasks))
@@ -143,7 +143,7 @@ func (r *SubagentRuns) Spawn(ctx context.Context, tasks []string, effort *string
                }
 
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("create worker: %w", err)
                }
 
                names = append(names, name)
@@ -157,19 +157,19 @@ func (r *SubagentRuns) Spawn(ctx context.Context, tasks []string, effort *string
 func (r *SubagentRuns) create(ctx context.Context, note, task string, view []string, effort string) (string, error) {
        subagent, err := r.subagents.Create(ctx)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("create worker log: %w", err)
        }
 
        for _, message := range [][2]string{{"note", note}, {"user", task}} {
                _, err = subagent.Append(message[0], message[1])
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("append worker message: %w", err)
                }
        }
 
        err = subagent.Flush(ctx)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("flush worker log: %w", err)
        }
 
        err = os.MkdirAll(subagent.WorkDir(), 0o700)
@@ -210,7 +210,11 @@ func (r *SubagentRuns) start(ctx context.Context, run *SubagentRun) error {
                r.mutex.Unlock()
                run.cancel()
 
-               return err
+               if err != nil {
+                       return fmt.Errorf("save running workers: %w", err)
+               }
+
+               return nil
        }
 
        r.workers.Add(1)
@@ -329,12 +333,12 @@ func (r *SubagentRuns) workPath() string {
 func (r *SubagentRuns) chain(ctx context.Context, run *SubagentRun) (string, error) {
        instructions, err := r.agent.SubagentPrompt()
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read worker instructions: %w", err)
        }
 
        log, err := run.subagent.Show()
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read worker log: %w", err)
        }
 
        input := make([]any, 0, len(run.view)+2)
@@ -410,13 +414,17 @@ func (r *SubagentRuns) Stop(name string) (bool, error) {
 func (r *SubagentRuns) Resume(ctx context.Context) error {
        names, err := r.load()
        if err != nil || len(names) == 0 {
-               return err
+               if err != nil {
+                       return fmt.Errorf("load interrupted workers: %w", err)
+               }
+
+               return nil
        }
 
        text := "The daemon restarted, cutting " + strings.Join(names, ", ") + "; nothing was lost: go on."
        err = r.note(text)
        if err != nil {
-               return err
+               return fmt.Errorf("record interrupted workers: %w", err)
        }
 
        for _, name := range names {
@@ -468,7 +476,7 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
 
                told, err := run.subagent.Append("user", message)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("append running worker message: %w", err)
                }
 
                run.pending = append(run.pending, told)
@@ -485,12 +493,12 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
 
        subagent, err := r.subagents.Get(ctx, name)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("open worker log: %w", err)
        }
 
        effort, err := r.effort(subagent)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read worker effort: %w", err)
        }
 
        err = r.memory.Settle(ctx)
@@ -500,7 +508,7 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
 
        view, err := r.layout.Split(r.memory.ViewLines(nil))
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("split worker view: %w", err)
        }
 
        _, err = subagent.Append("user", message)
@@ -509,7 +517,7 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
        }
 
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("save worker message: %w", err)
        }
 
        err = r.start(ctx, &SubagentRun{
@@ -518,7 +526,7 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
                effort:   effort,
        })
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("start told worker: %w", err)
        }
 
        return "Sent. " + name + "'s reply will reach you as a message.", nil
@@ -529,7 +537,7 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string,
 func (r *SubagentRuns) effort(subagent *Subagent) (string, error) {
        note, err := subagent.First()
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("read worker note: %w", err)
        }
 
        effort, own := strings.CutPrefix(note.Text, subagentNote+subagentNoteEffort)
index 708b515745074ff889391970eda186b32cf6ed71..8c9205facbfe574e26b1d7f903ec83fa7f187eb0 100644 (file)
@@ -357,7 +357,7 @@ func TestTellToAFinishedSubagentGoesOnFromItsLog(t *testing.T) {
        require.Equal(t, "work: "+subagentReport(name, []string{"Reply 2."}), test.Log(t)[2])
 
        _, err = runs.Tell(t.Context(), "BlueFox", "Hello.")
-       require.EqualError(t, err, "no agent BlueFox")
+       require.EqualError(t, err, "open worker log: no agent BlueFox")
 }
 
 func TestStopEndsASubagentAndItsCommand(t *testing.T) {
@@ -522,12 +522,12 @@ func TestRestartReportsASubagentThatCannotGoOn(t *testing.T) {
        events.WaitFor(t, "idle", 1)
        require.Equal(t, []string{
                "note: The daemon restarted, cutting BlueFox; nothing was lost: go on.",
-               "work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
+               "work: " + subagentReport("BlueFox", []string{"failed: open worker log: no agent BlueFox"}),
                "reply: a line",
        }, subagentTestChat(t, memory))
 
        require.Equal(t, subagentFailed, events.LastEvent("work").Status)
-       require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
+       require.Equal(t, "<3>subagent lost at restart name=BlueFox: open worker log: no agent BlueFox\n", logs.String())
 
        data, err := os.ReadFile(filepath.Join(agent.dir, subagentWorkFileName))
        require.NoError(t, err)
index 306f8f5ab293a438da20411250b92ba21c4334d2..85b0f44d1ed368cfcb340f0aa7f0edcd9fa3e9fc 100644 (file)
@@ -264,7 +264,7 @@ func (b *TelegramBot) send(ctx context.Context) {
 func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) error {
        offset, err := telegramLoadOffset(b.offsetPath)
        if err != nil {
-               return err
+               return fmt.Errorf("load Telegram offset: %w", err)
        }
 
        outage := &TelegramOutage{
@@ -313,7 +313,11 @@ poll:
                                        continue poll
                                }
 
-                               return err
+                               if err != nil {
+                                       return fmt.Errorf("handle Telegram update: %w", err)
+                               }
+
+                               return nil
                        }
 
                        imageOutage.End()
@@ -323,7 +327,7 @@ poll:
                        offset = update.UpdateID + 1
                        err = telegramSaveOffset(b.offsetPath, offset)
                        if err != nil {
-                               return err
+                               return fmt.Errorf("save Telegram offset: %w", err)
                        }
                }
        }
@@ -476,7 +480,7 @@ func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsAp
        default:
                _, err := l.Submit(telegramSource, message.Text)
                if err != nil {
-                       return err
+                       return fmt.Errorf("submit Telegram text: %w", err)
                }
 
                err = l.Flush(context.WithoutCancel(ctx))
index da0d78aa4fb6c95160ed1f42fb77631820a426d2..db259fb15c75c3d4ea2221a35812111f3ba5f3aa 100644 (file)
@@ -30,13 +30,13 @@ var errTerminalDaemonClosed = errors.New("the daemon closed the connection")
 func terminal(ctx context.Context, in io.Reader, out io.Writer) error {
        dir, err := dataDir()
        if err != nil {
-               return err
+               return fmt.Errorf("find data directory: %w", err)
        }
 
        path := filepath.Join(dir, socketFileName)
        err = checkSocketPath(path)
        if err != nil {
-               return err
+               return fmt.Errorf("check terminal socket path: %w", err)
        }
 
        var dialer net.Dialer
index 191bb251df369a99984dbbc683ac593b13b9d364..24e0fdb346acbbb486f5e1ca607968e35aca6d99 100644 (file)
--- a/tools.go
+++ b/tools.go
@@ -419,7 +419,7 @@ func (b *Toolbox) zoomAgent(ctx context.Context, name string) (string, error) {
 
        subagent, err := b.subagents.Get(ctx, name)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("open worker log: %w", err)
        }
 
        return subagent.Show()
index 3075cbfef2f2ee217de06386a1d9296279daab9c..c03e7fc2277164f8cd00610e25863b152515f2dc 100644 (file)
@@ -185,7 +185,7 @@ func TestZoomWithANameGivesTheWholeChatOfTheAgent(t *testing.T) {
        output := box.run(t.Context(), "zoom", `{"id":0,"n":1,"name":"`+subagent.name+`"}`)
        require.Equal(t, "0|note: subagent\n\n1|user: Count the files.", output)
 
-       require.Equal(t, "error: no agent BlueFox", box.run(t.Context(), "zoom", `{"id":0,"n":1,"name":"BlueFox"}`))
+       require.Equal(t, "error: open worker log: no agent BlueFox", box.run(t.Context(), "zoom", `{"id":0,"n":1,"name":"BlueFox"}`))
        require.Equal(t, "error: line 0+1 goes past the last message, -1", box.run(t.Context(), "zoom", `{"id":0,"n":1,"name":null}`))
 }
 
index df43e8e3d9b6026377e0eba90f3874b91b9277b1..27dd3c3e88a706ee2e6a18e4ad32930c522d736c 100644 (file)
@@ -197,7 +197,7 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 
        w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName))
        if err != nil {
-               return nil, err
+               return nil, fmt.Errorf("open WhatsApp state: %w", err)
        }
 
        w.inbox = newWhatsAppInbox(w.state, clock, w.groups, &ImageStore{dir: filepath.Join(dir, imageDirName)}, logger)
@@ -217,7 +217,7 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 
        err = w.openSession(ctx)
        if err != nil {
-               return nil, err
+               return nil, fmt.Errorf("open WhatsApp session: %w", err)
        }
 
        if w.device.ID != nil {
@@ -239,7 +239,7 @@ func (w *WhatsApp) sessionPath() string {
 func (w *WhatsApp) openSession(ctx context.Context) error {
        address, err := whatsappSessionAddress(w.sessionPath())
        if err != nil {
-               return err
+               return fmt.Errorf("find WhatsApp session address: %w", err)
        }
 
        container, err := sqlstore.New(ctx, "sqlite", address, waLog.Noop)
@@ -323,14 +323,18 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
        for {
                err := w.inbox.deliver(ctx, l, w.link(), false)
                if err != nil {
-                       return err
+                       return fmt.Errorf("deliver WhatsApp inbox: %w", err)
                }
 
                select {
                case <-ctx.Done():
                        return nil
                case err = <-w.inbox.failures:
-                       return err
+                       if err != nil {
+                               return fmt.Errorf("receive WhatsApp inbox: %w", err)
+                       }
+
+                       return nil
                case <-w.inbox.wake:
                case <-w.inbox.due():
                case <-retry:
@@ -340,7 +344,7 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
 
                        err = w.loggedOut(ctx, l)
                        if err != nil {
-                               return err
+                               return fmt.Errorf("handle WhatsApp logout: %w", err)
                        }
                case request := <-w.requests:
                        cancel()
@@ -355,7 +359,7 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
 
                                err = w.unlink(ctx, l, request.answer)
                                if err != nil {
-                                       return err
+                                       return fmt.Errorf("unlink WhatsApp: %w", err)
                                }
                        case w.isLinked():
                                request.answer("WhatsApp is linked already. Run /whatsapp unlink first.")
@@ -885,7 +889,7 @@ func (w *WhatsApp) newDevice(ctx context.Context) (*store.Device, error) {
 
                err = w.openSession(ctx)
                if err != nil {
-                       return nil, err
+                       return nil, fmt.Errorf("open new WhatsApp session: %w", err)
                }
        }
 
index 5010a74c70c79d44b29678c500ba4ccb36acb11b..8bf8a97eaee2dfa431012a539d9af667d4829952 100644 (file)
@@ -129,7 +129,7 @@ func (i *WhatsAppInbox) deliver(ctx context.Context, l *TurnLoop, link WhatsAppL
        }
 
        if err != nil {
-               return err
+               return fmt.Errorf("close due WhatsApp batches: %w", err)
        }
 
        batches := i.state.Closed()
@@ -183,7 +183,7 @@ func (i *WhatsAppInbox) deliver(ctx context.Context, l *TurnLoop, link WhatsAppL
 
                err = i.state.Remove(batch.Number)
                if err != nil {
-                       return err
+                       return fmt.Errorf("remove delivered WhatsApp batch: %w", err)
                }
 
                if batch.Turn() {
index b3bbe95bac3b789034bd954e1c35623974baeede..dbbf8624da26ad2c4389126c51e02683beff230e 100644 (file)
@@ -51,7 +51,7 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
 
        jid, err := whatsappSendCheck(id, chat, text, image, link.own)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("check WhatsApp send: %w", err)
        }
 
        digest := sha256.Sum256([]byte(text))
@@ -88,7 +88,7 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
        record.MessageID = link.client.GenerateMessageID()
        err = state.PutSend(record)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("save pending WhatsApp send: %w", err)
        }
 
        message := &waE2E.Message{Conversation: new(text)}
@@ -128,7 +128,7 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
 
        err = state.PutSend(record)
        if err != nil {
-               return "", err
+               return "", fmt.Errorf("save completed WhatsApp send: %w", err)
        }
 
        return record.Answer, nil
index df82e3cb3f483e967e6ebfff2cc0a32b6866a1b4..977d06b08056a46510ed7c7bdd969bdc0e20b415 100644 (file)
@@ -190,7 +190,7 @@ func TestWhatsAppSendRefuses(t *testing.T) {
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        _, err := whatsapp.Send(t.Context(), tc.id, tc.chat, tc.text, "")
-                       require.EqualError(t, err, tc.err)
+                       require.EqualError(t, err, "check WhatsApp send: "+tc.err)
                })
        }