]> git.giacomolli.uk - onechat/commitdiff
Complete context on forwarded local errors
authorAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:57:25 +0000 (11:57 -0300)
committerAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:57:25 +0000 (11:57 -0300)
Forwarded failures from local setup, scans, persistence, and command
execution identify their operation without losing the original cause.
Successful results remain unchanged, and privacy-safe Telegram
diagnostics keep their existing contract. Tests cover nil results for
missing nodes and cause identity after read or sync failures.

Issue: 3cdc678

19 files changed:
agent.go
chatgpt.go
command.go
daemon.go
journal.go
log.go
memory.go
model.go
probe.go
schedule.go
subagent.go
subagent_run.go
subagent_run_test.go
syncer_test.go
terminal.go
tools.go
tree.go
tree_test.go
view.go

index fbea57f166e546b5600e699d2e902d657cf68cc4..da4b0b7be7eb5c65bc474095cd5eb00f189c4840 100644 (file)
--- a/agent.go
+++ b/agent.go
@@ -61,7 +61,12 @@ func openAgent(dir string) (*Agent, error) {
        path := filepath.Join(dir, "config.json")
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
        path := filepath.Join(dir, "config.json")
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
-               return createAgent(dir)
+               agent, err := createAgent(dir)
+               if err != nil {
+                       return agent, fmt.Errorf("create agent: %w", err)
+               }
+
+               return agent, nil
        }
 
        if err != nil {
        }
 
        if err != nil {
@@ -348,7 +353,12 @@ func createAgent(dir string) (*Agent, error) {
                return nil, fmt.Errorf("prepare agent defaults: %w", err)
        }
 
                return nil, fmt.Errorf("prepare agent defaults: %w", err)
        }
 
-       return writeAgent(dir, config)
+       agent, err := writeAgent(dir, config)
+       if err != nil {
+               return agent, fmt.Errorf("write new agent: %w", err)
+       }
+
+       return agent, nil
 }
 
 // defaultAgentConfig returns the settings of a new agent, without an ID. The
 }
 
 // defaultAgentConfig returns the settings of a new agent, without an ID. The
index 49be0150264f31dfdabc28b4c0626a54ac71f8e3..a5d9ee3c75d508b25f549636af6b07bb728cc5e9 100644 (file)
@@ -458,5 +458,10 @@ func chatgptLogin(ctx context.Context, out io.Writer) (err error) {
                err = errors.Join(err, tokens.Close())
        }()
 
                err = errors.Join(err, tokens.Close())
        }()
 
-       return newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer).Login(ctx, out)
+       err = newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer).Login(ctx, out)
+       if err != nil {
+               return fmt.Errorf("complete device login: %w", err)
+       }
+
+       return nil
 }
 }
index 02901e2a8f93a3df841e4fbc23101a38abd15420..fefb662da21039a37d44f0ccaca750abe2d6c0f7 100644 (file)
@@ -54,15 +54,40 @@ func parseCommand(ctx context.Context, args []string) error {
        // A signal is the normal stop of the daemon and the client, so their
        // result does not include ctx.Err().
        case len(args) == 1 && args[0] == "daemon":
        // A signal is the normal stop of the daemon and the client, so their
        // result does not include ctx.Err().
        case len(args) == 1 && args[0] == "daemon":
-               return daemon(ctx, os.Stdout)
+               err := daemon(ctx, os.Stdout)
+               if err != nil {
+                       return fmt.Errorf("run daemon: %w", err)
+               }
+
+               return nil
        case len(args) == 0 || len(args) == 1 && args[0] == "chat":
        case len(args) == 0 || len(args) == 1 && args[0] == "chat":
-               return terminal(ctx, os.Stdin, os.Stdout)
+               err := terminal(ctx, os.Stdin, os.Stdout)
+               if err != nil {
+                       return fmt.Errorf("run terminal: %w", err)
+               }
+
+               return nil
        case len(args) == 2 && args[0] == "backup":
        case len(args) == 2 && args[0] == "backup":
-               return backup(ctx, args[1], os.Stdout)
+               err := backup(ctx, args[1], os.Stdout)
+               if err != nil {
+                       return fmt.Errorf("run backup: %w", err)
+               }
+
+               return nil
        case len(args) == 2 && args[0] == "restore":
        case len(args) == 2 && args[0] == "restore":
-               return restore(ctx, args[1], os.Stdout)
+               err := restore(ctx, args[1], os.Stdout)
+               if err != nil {
+                       return fmt.Errorf("run restore: %w", err)
+               }
+
+               return nil
        case len(args) > 0 && args[0] == "import":
        case len(args) > 0 && args[0] == "import":
-               return whatsappImport(ctx, args[1:], os.Stdout)
+               err := whatsappImport(ctx, args[1:], os.Stdout)
+               if err != nil {
+                       return fmt.Errorf("import WhatsApp history: %w", err)
+               }
+
+               return nil
        case len(args) > 0 && args[0] == "probe":
                err := probe(ctx, args[1:], os.Stdout)
                if err != nil {
        case len(args) > 0 && args[0] == "probe":
                err := probe(ctx, args[1:], os.Stdout)
                if err != nil {
index 962070a11e7f38b84709173e96b3c17395b13a6c..7945c2b5638a49f81aa4f1369bca67ea0327cadd 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -359,9 +359,14 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
                logger.Info("Telegram bot started")
        }
 
                logger.Info("Telegram bot started")
        }
 
-       return daemonServe(ctx, listener, telegram, whatsapp, schedules, func(notify func(Event)) (*TurnLoop, error) {
+       err = daemonServe(ctx, listener, telegram, whatsapp, schedules, func(notify func(Event)) (*TurnLoop, error) {
                return root.Loop(whatsapp, notify)
        }, agents[1:]...)
                return root.Loop(whatsapp, notify)
        }, agents[1:]...)
+       if err != nil {
+               return fmt.Errorf("serve daemon: %w", err)
+       }
+
+       return nil
 }
 
 // daemonListenSocket listens on the Unix socket at path, with mode 0600. The
 }
 
 // daemonListenSocket listens on the Unix socket at path, with mode 0600. The
index 6251e8c7d04b2f38749babc1b3f8b454a26563ea..d048913b6e6c069b4c71a4b46b47087cd7d6dfd5 100644 (file)
@@ -215,7 +215,12 @@ func journalScanFile(ctx context.Context, path string, day uint32, offset uint64
                }
 
                if errors.Is(err, io.EOF) && last {
                }
 
                if errors.Is(err, io.EOF) && last {
-                       return journalCutFile(path, offset)
+                       err = journalCutFile(path, offset)
+                       if err != nil {
+                               return fmt.Errorf("cut incomplete day line: %w", err)
+                       }
+
+                       return nil
                }
 
                if errors.Is(err, io.EOF) {
                }
 
                if errors.Is(err, io.EOF) {
diff --git a/log.go b/log.go
index 31e17898855d4130dcf08b9f01a6a8ac35db0809..61f4d4c449e97288e92bc3734d5643b512237243 100644 (file)
--- a/log.go
+++ b/log.go
@@ -121,7 +121,7 @@ func (l *Log) catchUp(ctx context.Context) error {
                }
        }
 
                }
        }
 
-       return l.journal.Scan(ctx, start, func(line []byte, place Position) error {
+       err := l.journal.Scan(ctx, start, func(line []byte, place Position) error {
                var message Message
 
                err := json.Unmarshal(line, &message)
                var message Message
 
                err := json.Unmarshal(line, &message)
@@ -138,8 +138,18 @@ func (l *Log) catchUp(ctx context.Context) error {
                        return fmt.Errorf("message %d: %w", message.ID, err)
                }
 
                        return fmt.Errorf("message %d: %w", message.ID, err)
                }
 
-               return l.addIndex(place)
+               err = l.addIndex(place)
+               if err != nil {
+                       return fmt.Errorf("index scanned message: %w", err)
+               }
+
+               return nil
        })
        })
+       if err != nil {
+               return fmt.Errorf("scan message journal: %w", err)
+       }
+
+       return nil
 }
 
 func (l *Log) addIndex(place Position) error {
 }
 
 func (l *Log) addIndex(place Position) error {
index be5810560dabf09ff9c5bdc52ed2a0bb6ae8c141..97be6a1b6547bab878d8fa01d3de91b89793d25a 100644 (file)
--- a/memory.go
+++ b/memory.go
@@ -270,7 +270,12 @@ func (m *Memory) catchUp() error {
                }
        }
 
                }
        }
 
-       return m.save()
+       err := m.save()
+       if err != nil {
+               return fmt.Errorf("save caught-up memory: %w", err)
+       }
+
+       return nil
 }
 
 // dispatch starts the builds that are ready, up to the limit. A build whose
 }
 
 // dispatch starts the builds that are ready, up to the limit. A build whose
@@ -311,7 +316,12 @@ func (m *Memory) build(node Node) error {
        m.chat.Refresh(node)
        m.cview.Refresh(node)
 
        m.chat.Refresh(node)
        m.cview.Refresh(node)
 
-       return m.settle(NodeKey{node.Level, node.Index})
+       err = m.settle(NodeKey{node.Level, node.Index})
+       if err != nil {
+               return fmt.Errorf("settle built node: %w", err)
+       }
+
+       return nil
 }
 
 // settle looks at the parent of the built node key when both children are
 }
 
 // settle looks at the parent of the built node key when both children are
index f630b3e8f50ab76f92261bd8111f9010f0f76d95..78836fe8e8d607208c966cfe11144f782e2a3c2d 100644 (file)
--- a/model.go
+++ b/model.go
@@ -344,7 +344,12 @@ func (c *ModelClient) send(httpRequest *http.Request, record *ModelUsageRecord)
                return ModelResponse{}, errors.Join(err, readErr)
        }
 
                return ModelResponse{}, errors.Join(err, readErr)
        }
 
-       return modelReadStream(httpResponse.Body)
+       response, err = modelReadStream(httpResponse.Body)
+       if err != nil {
+               return response, fmt.Errorf("read model response stream: %w", err)
+       }
+
+       return response, nil
 }
 
 // Write appends record to the file with one write.
 }
 
 // Write appends record to the file with one write.
index a1ee38d17d6e43ff94c994e0c3a8f61d3b86f627..d896fcb0f534929fe1fb9df0fd0909ab3263e077 100644 (file)
--- a/probe.go
+++ b/probe.go
@@ -42,7 +42,12 @@ func (s ProbeSlotSummarizer) Summarize(ctx context.Context, task Task) (string,
                <-s.slots
        }()
 
                <-s.slots
        }()
 
-       return s.inner.Summarize(ctx, task)
+       text, err := s.inner.Summarize(ctx, task)
+       if err != nil {
+               return text, fmt.Errorf("summarize probe node: %w", err)
+       }
+
+       return text, nil
 }
 
 // probe runs "onechat probe LOG COUNT [BUILDS]". It appends the first COUNT
 }
 
 // probe runs "onechat probe LOG COUNT [BUILDS]". It appends the first COUNT
index 23462d292e634b70906646d88def5ea7712abef8..3c3456ddbcc6363be07b9398b01b737c00653366 100644 (file)
@@ -286,7 +286,12 @@ func (s *Schedules) Done(schedule Schedule, now time.Time) error {
        if schedule.Cron == "" {
                s.file.Schedules = slices.Delete(s.file.Schedules, index, index+1)
 
        if schedule.Cron == "" {
                s.file.Schedules = slices.Delete(s.file.Schedules, index, index+1)
 
-               return s.save()
+               err := s.save()
+               if err != nil {
+                       return fmt.Errorf("save completed one-time schedule: %w", err)
+               }
+
+               return nil
        }
 
        cron, err := scheduleParseCron(schedule.Cron)
        }
 
        cron, err := scheduleParseCron(schedule.Cron)
@@ -298,12 +303,22 @@ func (s *Schedules) Done(schedule Schedule, now time.Time) error {
        if !found {
                s.file.Schedules = slices.Delete(s.file.Schedules, index, index+1)
 
        if !found {
                s.file.Schedules = slices.Delete(s.file.Schedules, index, index+1)
 
-               return s.save()
+               err = s.save()
+               if err != nil {
+                       return fmt.Errorf("save exhausted schedule: %w", err)
+               }
+
+               return nil
        }
 
        s.file.Schedules[index].Due = next
 
        }
 
        s.file.Schedules[index].Due = next
 
-       return s.save()
+       err = s.save()
+       if err != nil {
+               return fmt.Errorf("save next schedule run: %w", err)
+       }
+
+       return nil
 }
 
 // sorted returns a copy of the schedules by due time, then by ID.
 }
 
 // sorted returns a copy of the schedules by due time, then by ID.
index 37e9b3a1bc8430f91eec0588edc423a1e8634721..9976a4aaed1023084a236de11f20f6a45300c42c 100644 (file)
@@ -89,7 +89,12 @@ func (s *Subagents) Create(ctx context.Context) (*Subagent, error) {
                        return nil, fmt.Errorf("create subagent directory: %w", err)
                }
 
                        return nil, fmt.Errorf("create subagent directory: %w", err)
                }
 
-               return s.Get(ctx, name)
+               subagent, err := s.Get(ctx, name)
+               if err != nil {
+                       return subagent, fmt.Errorf("open created worker: %w", err)
+               }
+
+               return subagent, nil
        }
 
        return nil, fmt.Errorf("no free subagent name after %d draws", subagentNameDraws)
        }
 
        return nil, fmt.Errorf("no free subagent name after %d draws", subagentNameDraws)
index e96975c90abd0cec2a59519b2bbcab18811ebcd4..54fa4304fe9312b55506ea58aa28b9a8fa867acb 100644 (file)
@@ -177,11 +177,16 @@ func (r *SubagentRuns) create(ctx context.Context, note, task string, view []str
                return "", fmt.Errorf("create work directory: %w", err)
        }
 
                return "", fmt.Errorf("create work directory: %w", err)
        }
 
-       return subagent.name, r.start(ctx, &SubagentRun{
+       err = r.start(ctx, &SubagentRun{
                subagent: subagent,
                view:     view,
                effort:   effort,
        })
                subagent: subagent,
                view:     view,
                effort:   effort,
        })
+       if err != nil {
+               return subagent.name, fmt.Errorf("start created worker: %w", err)
+       }
+
+       return subagent.name, nil
 }
 
 // start runs run in the background and logs its start. Its context keeps the
 }
 
 // start runs run in the background and logs its start. Its context keeps the
@@ -369,7 +374,12 @@ func (r *SubagentRuns) chain(ctx context.Context, run *SubagentRun) (string, err
                },
        }
 
                },
        }
 
-       return chain.Run(ctx, input)
+       text, err := chain.Run(ctx, input)
+       if err != nil {
+               return text, fmt.Errorf("run worker chain: %w", err)
+       }
+
+       return text, nil
 }
 
 // take returns the messages of Tell that wait for a request of run, and removes
 }
 
 // take returns the messages of Tell that wait for a request of run, and removes
@@ -440,7 +450,12 @@ func (r *SubagentRuns) Resume(ctx context.Context) error {
        r.mutex.Lock()
        defer r.mutex.Unlock()
 
        r.mutex.Lock()
        defer r.mutex.Unlock()
 
-       return r.save()
+       err = r.save()
+       if err != nil {
+               return fmt.Errorf("save resumed workers: %w", err)
+       }
+
+       return nil
 }
 
 // load reads the names of work.json. A missing file names none.
 }
 
 // load reads the names of work.json. A missing file names none.
index 8c9205facbfe574e26b1d7f903ec83fa7f187eb0..047e6a874e97cc13b8c193b88b0b208a5b8f1b8e 100644 (file)
@@ -200,12 +200,12 @@ 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: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
+               "work: [" + name + "]\nfailed: run worker chain: 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, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
+       require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: run worker chain: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
 }
 
 func TestSubagentHasItsOwnEffortAndNoSpawn(t *testing.T) {
 }
 
 func TestSubagentHasItsOwnEffortAndNoSpawn(t *testing.T) {
index d463eecbcf7a2babd89b3e22cd04d8c91df38378..8eeb1eeee55213541def549d08b8b4772b0508ee 100644 (file)
@@ -76,16 +76,17 @@ func TestFlushWithoutWritesNeedsNoSync(t *testing.T) {
 }
 
 func TestAFailedSyncFailsEachLaterFlush(t *testing.T) {
 }
 
 func TestAFailedSyncFailsEachLaterFlush(t *testing.T) {
+       failure := errors.New("disk error")
        s := newSyncer(func() error {
        s := newSyncer(func() error {
-               return errors.New("disk error")
+               return failure
        })
 
        s.Written()
        })
 
        s.Written()
-       require.ErrorContains(t, s.Flush(t.Context()), "disk error")
+       require.ErrorIs(t, s.Flush(t.Context()), failure)
 
        s.Written()
 
        s.Written()
-       require.ErrorContains(t, s.Flush(t.Context()), "disk error")
-       require.ErrorContains(t, s.Close(), "disk error")
+       require.ErrorIs(t, s.Flush(t.Context()), failure)
+       require.ErrorIs(t, s.Close(), failure)
 }
 
 func TestMemoryFlushSyncsTheChat(t *testing.T) {
 }
 
 func TestMemoryFlushSyncsTheChat(t *testing.T) {
index db259fb15c75c3d4ea2221a35812111f3ba5f3aa..340a8aa1e54118ecff9a06feffd3a93acc905119 100644 (file)
@@ -46,7 +46,12 @@ func terminal(ctx context.Context, in io.Reader, out io.Writer) error {
                return fmt.Errorf(`connect to the daemon (start it with "onechat daemon"): %w`, err)
        }
 
                return fmt.Errorf(`connect to the daemon (start it with "onechat daemon"): %w`, err)
        }
 
-       return terminalSession(ctx, conn, in, out)
+       err = terminalSession(ctx, conn, in, out)
+       if err != nil {
+               return fmt.Errorf("run terminal session: %w", err)
+       }
+
+       return nil
 }
 
 // terminalSession runs the client on conn until the input ends, the daemon
 }
 
 // terminalSession runs the client on conn until the input ends, the daemon
index 24e0fdb346acbbb486f5e1ca607968e35aca6d99..24e745bb51192a2575472c83d5e3f69670dad2e7 100644 (file)
--- a/tools.go
+++ b/tools.go
@@ -422,7 +422,12 @@ func (b *Toolbox) zoomAgent(ctx context.Context, name string) (string, error) {
                return "", fmt.Errorf("open worker log: %w", err)
        }
 
                return "", fmt.Errorf("open worker log: %w", err)
        }
 
-       return subagent.Show()
+       text, err := subagent.Show()
+       if err != nil {
+               return text, fmt.Errorf("read worker log: %w", err)
+       }
+
+       return text, nil
 }
 
 // zoom returns message id whole for n = 1, or else the two lines under the line
 }
 
 // zoom returns message id whole for n = 1, or else the two lines under the line
diff --git a/tree.go b/tree.go
index db93b1d70357bb98e893e6ee6484b05570b24885..765016eaba3e14c1ebd2dba4b5b30356346418e4 100644 (file)
--- a/tree.go
+++ b/tree.go
@@ -122,7 +122,7 @@ func (t *Tree) open(ctx context.Context, total int) error {
                }
        }
 
                }
        }
 
-       return t.journal.Scan(ctx, start, func(line []byte, place Position) error {
+       err = t.journal.Scan(ctx, start, func(line []byte, place Position) error {
                var node Node
 
                err := json.Unmarshal(line, &node)
                var node Node
 
                err := json.Unmarshal(line, &node)
@@ -132,15 +132,25 @@ func (t *Tree) open(ctx context.Context, total int) error {
 
                err = t.check(node)
                if err != nil {
 
                err = t.check(node)
                if err != nil {
-                       return err
+                       return fmt.Errorf("validate scanned node: %w", err)
                }
 
                if nodeEnd(node.Level, node.Index) > total {
                        return fmt.Errorf("node (%d, %d) covers messages after the log", node.Level, node.Index)
                }
 
                }
 
                if nodeEnd(node.Level, node.Index) > total {
                        return fmt.Errorf("node (%d, %d) covers messages after the log", node.Level, node.Index)
                }
 
-               return t.addIndex(NodeKey{node.Level, node.Index}, place)
+               err = t.addIndex(NodeKey{node.Level, node.Index}, place)
+               if err != nil {
+                       return fmt.Errorf("index scanned node: %w", err)
+               }
+
+               return nil
        })
        })
+       if err != nil {
+               return fmt.Errorf("scan node journal: %w", err)
+       }
+
+       return nil
 }
 
 // Close closes the files of the tree.
 }
 
 // Close closes the files of the tree.
@@ -188,7 +198,12 @@ func (t *Tree) writeLevel(node NodeKey, place Position) error {
        record := make([]byte, positionBytes)
        encodePosition(record, place)
 
        record := make([]byte, positionBytes)
        encodePosition(record, place)
 
-       return level.Write(node.index, record)
+       err = level.Write(node.index, record)
+       if err != nil {
+               return fmt.Errorf("write node level record: %w", err)
+       }
+
+       return nil
 }
 
 // check makes sure that node is valid and can join the tree now.
 }
 
 // check makes sure that node is valid and can join the tree now.
@@ -240,7 +255,12 @@ func (t *Tree) addIndex(node NodeKey, place Position) error {
                return fmt.Errorf("append node order: %w", err)
        }
 
                return fmt.Errorf("append node order: %w", err)
        }
 
-       return t.writeLevel(node, place)
+       err = t.writeLevel(node, place)
+       if err != nil {
+               return fmt.Errorf("write indexed node level: %w", err)
+       }
+
+       return nil
 }
 
 // level returns the index of level, and opens it at first use.
 }
 
 // level returns the index of level, and opens it at first use.
index 2f23d4d22d3eebcd182fb20a06a10da895dff9fc..37ecc9489de5c99eb75edb61f7353da931d728ac 100644 (file)
@@ -61,6 +61,36 @@ func TestTreeBuildsEachNodeOnce(t *testing.T) {
        require.Equal(t, "user: a", treeReadNode(t, tree, 0, 0).Text)
 }
 
        require.Equal(t, "user: a", treeReadNode(t, tree, 0, 0).Text)
 }
 
+func TestTreeMissingNodeHasNoErrorAfterWrapping(t *testing.T) {
+       tree, err := openTree(t.Context(), t.TempDir(), 0, newTestClock().Now)
+       require.NoError(t, err)
+
+       t.Cleanup(func() {
+               require.NoError(t, tree.Close())
+       })
+
+       _, built, err := tree.Node(0, 0)
+       require.NoError(t, err)
+       require.False(t, built)
+}
+
+func TestTreeReadFailureKeepsItsCause(t *testing.T) {
+       tree, err := openTree(t.Context(), t.TempDir(), 1, newTestClock().Now)
+       require.NoError(t, err)
+       t.Cleanup(func() {
+               require.NoError(t, tree.Close())
+       })
+
+       require.NoError(t, tree.Add(treeTestNode(0, 0, "user: a")))
+       require.NoError(t, tree.levels[0].Close())
+
+       _, built, err := tree.Node(0, 0)
+       delete(tree.levels, 0)
+       require.ErrorIs(t, err, os.ErrClosed)
+       require.False(t, built)
+       require.ErrorContains(t, err, "find node position")
+}
+
 func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
        root := t.TempDir()
        clock := newTestClock()
 func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
        root := t.TempDir()
        clock := newTestClock()
diff --git a/view.go b/view.go
index 8877462f6b028c80f54fc23a5d6a5f621545b1fe..9af1c21476fccfb2d1aa7ba394d8a3120ef2c35b 100644 (file)
--- a/view.go
+++ b/view.go
@@ -94,7 +94,12 @@ func (v *View) Append(tree ViewNodeReader) (bool, error) {
        v.lines = append(v.lines, line)
        v.end++
 
        v.lines = append(v.lines, line)
        v.end++
 
-       return v.fit(tree)
+       merged, err := v.fit(tree)
+       if err != nil {
+               return merged, fmt.Errorf("fit appended view: %w", err)
+       }
+
+       return merged, nil
 }
 
 // load keeps the node of line when it is built, and tells if it is.
 }
 
 // load keeps the node of line when it is built, and tells if it is.