From d50f3670bf2ccb843e9001321d072a2d5680cb2a Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 22:24:38 +0200 Subject: plan 10: a step that ran but could not be logged is named in the error --- internal/engine/apply.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'internal/engine/apply.go') diff --git a/internal/engine/apply.go b/internal/engine/apply.go index 09f3aab..cc87f30 100644 --- a/internal/engine/apply.go +++ b/internal/engine/apply.go @@ -113,7 +113,10 @@ func (e *Engine) applyFile(dirName string, c plan.Chain, approved bool, j *journ // Each step is logged the moment it has run (review M9), not after the // whole chain: a run killed mid-chain must leave what it did undoable. results, err := apply.ChainLogged(c, func(i int, sr apply.StepResult) error { - return e.logStep(j, run, dirName, rel, i+1, c.Steps[i], sr) + if err := e.logStep(j, run, dirName, rel, i+1, c.Steps[i], sr); err != nil { + return unloggedStep(rel, c.Steps[i], sr, err) + } + return nil }) if err != nil { return FileResult{}, err @@ -121,6 +124,25 @@ func (e *Engine) applyFile(dirName string, c plan.Chain, approved bool, j *journ return FileResult{File: c.File, Steps: results}, nil } +// unloggedStep is the error for a step whose log entry could not be written +// (re-review N1). A step that ran is named with where its file is now: +// undo cannot see it, so the user must be told where to look. +func unloggedStep(rel string, step plan.Step, sr apply.StepResult, err error) error { + if sr.Status != "ok" { + return fmt.Errorf("%s: step %s (%s) could not be logged: %w", rel, actionName(step.Kind), sr.Status, err) + } + var where string + switch { + case step.Kind == plan.Copy: + where = "a copy is at " + xdg.Abbrev(sr.Dst) + case sr.Dst != "": + where = "the file is now at " + xdg.Abbrev(sr.Dst) + default: + where = "the file is deleted for good" + } + return fmt.Errorf("%s: %s ran but could not be logged, so undo cannot see it (%s): %w", rel, actionName(step.Kind), where, err) +} + // tallyFile updates result's Applied/Failed/Declined counters from one // file's step outcomes. The three are not mutually exclusive: a chain that // ran one step ok and then failed on the next counts toward both Applied -- cgit v1.3