From 05d8b48fa99c125a33bcde54144885936b8b49d0 Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Mon, 25 May 2026 14:58:14 -0700 Subject: [PATCH 1/2] fix(run): use isolated temp directory and socket to avoid conflict with service ephemerd run was sharing the same data directory and named pipe as the running service, causing BoltDB to hang waiting for a write lock. Use a unique temp directory and (on Windows) a unique named pipe per run. --- cmd/ephemerd/run.go | 22 ++++++++++++++++++++-- pkg/workflow/runner.go | 10 ++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cmd/ephemerd/run.go b/cmd/ephemerd/run.go index e8fd85f..d119eee 100644 --- a/cmd/ephemerd/run.go +++ b/cmd/ephemerd/run.go @@ -131,9 +131,27 @@ func runWorkflow(ctx context.Context, workflowPath string, jobFilter string) err return nil } + // Use an isolated temp directory so the run command doesn't conflict with + // the ephemerd service (BoltDB is single-writer and they'd share the same + // data directory and named pipe otherwise). + tmpDir, err := os.MkdirTemp("", "ephemerd-run-*") + if err != nil { + return fmt.Errorf("creating temp directory: %w", err) + } + defer os.RemoveAll(tmpDir) + + // On Windows, derive a unique named pipe so we don't collide with the + // service's \\.\pipe\ephemerd-containerd. On Unix the socket lives + // inside DataDir which is already unique. + var socketPath string + if runtime.GOOS == "windows" { + socketPath = `\\.\pipe\ephemerd-run-` + filepath.Base(tmpDir) + } + runner := &workflow.Runner{ - DataDir: configDir, - Log: log, + DataDir: tmpDir, + SocketPath: socketPath, + Log: log, } return runner.RunJob(ctx, jobName, job, repoDir) diff --git a/pkg/workflow/runner.go b/pkg/workflow/runner.go index a72f8aa..0fd8526 100644 --- a/pkg/workflow/runner.go +++ b/pkg/workflow/runner.go @@ -26,8 +26,9 @@ const ( // Runner executes workflow jobs locally using embedded containerd. type Runner struct { - DataDir string - Log *slog.Logger + DataDir string + SocketPath string // optional: containerd socket override for isolation from the service + Log *slog.Logger } // gitInfo holds repository metadata sniffed from the local git repo. @@ -48,8 +49,9 @@ func (r *Runner) RunJob(ctx context.Context, jobName string, job Job, repoDir st // Start embedded containerd r.Log.Info("starting containerd") ctrd, err := ctdpkg.New(ctdpkg.Config{ - DataDir: r.DataDir, - Log: r.Log, + DataDir: r.DataDir, + SocketPath: r.SocketPath, + Log: r.Log, }) if err != nil { return fmt.Errorf("starting containerd: %w", err) From d9ca513d6fbf38e4d5831fb1d1a7db52e864186a Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Mon, 25 May 2026 15:22:57 -0700 Subject: [PATCH 2/2] fix(run): handle os.RemoveAll error properly --- cmd/ephemerd/run.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/ephemerd/run.go b/cmd/ephemerd/run.go index d119eee..e81e9e2 100644 --- a/cmd/ephemerd/run.go +++ b/cmd/ephemerd/run.go @@ -138,7 +138,11 @@ func runWorkflow(ctx context.Context, workflowPath string, jobFilter string) err if err != nil { return fmt.Errorf("creating temp directory: %w", err) } - defer os.RemoveAll(tmpDir) + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.Warn("failed to clean up temp directory", "dir", tmpDir, "error", err) + } + }() // On Windows, derive a unique named pipe so we don't collide with the // service's \\.\pipe\ephemerd-containerd. On Unix the socket lives