From f692ad592cce4e0f43d0bad577032479992a3ca8 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Sat, 2 May 2026 13:22:01 +0100 Subject: [PATCH] restic: treat 'config file already exists' on init as soft success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-running restic init on a repo that's already initialised exits non-zero with "Fatal: ... config file already exists". Semantically that's a no-op, not a failure — the repo IS initialised, the caller's intent is satisfied. Sniff stderr for the magic string and swallow the exit code in that case, emitting an event line so the operator-facing log says what happened. Caught while smoke-testing P2-04.5: I'd init'd the repo manually during a debug session, then the operator clicking the UI's Init-repo button would hit this and the host's repo_initialised_at would never flip. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/restic/runner.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/internal/restic/runner.go b/internal/restic/runner.go index 41b738a..7e40e7f 100644 --- a/internal/restic/runner.go +++ b/internal/restic/runner.go @@ -173,15 +173,36 @@ func (e Env) RunInit(ctx context.Context, handle LineHandler) error { return fmt.Errorf("restic init: start: %w", err) } + // Sniff for "config file already exists" on stderr; if we see it + // we'll treat the non-zero exit as a soft success — running init + // against an already-initialised repo is a no-op semantically, + // not a failure. Wraps the caller's handle so the line still + // gets streamed verbatim to the operator-facing log. + alreadyInited := false + sniff := func(stream, line string, ev any) { + if stream == "stderr" && strings.Contains(line, "config file already exists") { + alreadyInited = true + } + if handle != nil { + handle(stream, line, ev) + } + } + done := make(chan error, 2) - go func() { done <- pumpPlain(stdout, "stdout", handle) }() - go func() { done <- pumpPlain(stderr, "stderr", handle) }() + go func() { done <- pumpPlain(stdout, "stdout", sniff) }() + go func() { done <- pumpPlain(stderr, "stderr", sniff) }() for i := 0; i < 2; i++ { if err := <-done; err != nil && handle != nil { handle("event", fmt.Sprintf("pump error: %v", err), nil) } } if werr := cmd.Wait(); werr != nil { + if alreadyInited { + if handle != nil { + handle("event", "repo already initialised — treating as success", nil) + } + return nil + } return fmt.Errorf("restic init: %w", werr) } return nil