Commit a29c053

Eric Bower  ·  2026-05-07 23:26:36 -0400 EDT
parent 8fc0a2e
fix: linter
1 files changed,  +64, -63
M main.go
+64, -63
  1@@ -85,7 +85,10 @@ func NewCfg() (*Cfg, string, bool) {
  2 	// Split args so the subcommand (first non-flag arg) doesn't block
  3 	// flags that appear after it: "pici runner --wait" works.
  4 	flags, cmd, wantHelp := splitCommand(os.Args[1:])
  5-	flag.CommandLine.Parse(flags)
  6+	if err := flag.CommandLine.Parse(flags); err != nil {
  7+		fmt.Fprintf(os.Stderr, "failed to parse flags: %v\n", err)
  8+		os.Exit(1)
  9+	}
 10 
 11 	logger := newLogger("ci", logLevel)
 12 	ctx, cancel := context.WithCancel(context.Background())
 13@@ -381,7 +384,11 @@ func (w *WorkspaceTar) Setup() error {
 14 	if err != nil {
 15 		return fmt.Errorf("open tar: %w", err)
 16 	}
 17-	defer f.Close()
 18+	defer func() {
 19+		if err := f.Close(); err != nil {
 20+			log.Error("close tar", "err", err)
 21+		}
 22+	}()
 23 
 24 	// Extract tar to temp dir
 25 	tr := tar.NewReader(f)
 26@@ -410,10 +417,14 @@ func (w *WorkspaceTar) Setup() error {
 27 				return fmt.Errorf("open %s: %w", target, err)
 28 			}
 29 			if _, err := io.Copy(tf, tr); err != nil {
 30-				tf.Close()
 31+				if closeErr := tf.Close(); closeErr != nil {
 32+					return fmt.Errorf("close %s: %w", target, closeErr)
 33+				}
 34 				return fmt.Errorf("write %s: %w", target, err)
 35 			}
 36-			tf.Close()
 37+			if err := tf.Close(); err != nil {
 38+				return fmt.Errorf("close %s: %w", target, err)
 39+			}
 40 		}
 41 	}
 42 
 43@@ -538,9 +549,9 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
 44 	jobID := generateJobID(eventData.Name, eventData.Workspace)
 45 	log = log.With("job_id", jobID)
 46 	eventBytes, _ := json.Marshal(eventData)
 47-	fmt.Fprintf(os.Stdout, "šŸš€ starting job ci.%s.%s\n", eventData.Name, jobID)
 48-	fmt.Fprintf(os.Stdout, "   event: type=%s name=%s workspace=%s\n", eventData.Type, eventData.Name, eventData.Workspace)
 49-	fmt.Fprintf(os.Stdout, "   %s\n", string(eventBytes))
 50+	fmt.Fprintf(os.Stdout, "šŸš€ starting job ci.%s.%s\n", eventData.Name, jobID)                                              //nolint:errcheck
 51+	fmt.Fprintf(os.Stdout, "   event: type=%s name=%s workspace=%s\n", eventData.Type, eventData.Name, eventData.Workspace) //nolint:errcheck
 52+	fmt.Fprintf(os.Stdout, "   %s\n", string(eventBytes))                                                                   //nolint:errcheck
 53 
 54 	wk := cfg.NewWorkspace(cfg, log, eventData.Workspace)
 55 	eng := &JobEngine{
 56@@ -556,11 +567,11 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
 57 		}
 58 	}()
 59 
 60-	fmt.Fprintf(os.Stdout, "šŸ“¦ syncing workspace %s\n", eventData.Workspace)
 61+	fmt.Fprintf(os.Stdout, "šŸ“¦ syncing workspace %s\n", eventData.Workspace) //nolint:errcheck
 62 	if err := eng.Setup(); err != nil {
 63 		return fmt.Errorf("setup: %w", err)
 64 	}
 65-	fmt.Fprintf(os.Stdout, "āœ… workspace ready %s\n", eng.Wk.GetDir())
 66+	fmt.Fprintf(os.Stdout, "āœ… workspace ready %s\n", eng.Wk.GetDir()) //nolint:errcheck
 67 
 68 	// Store the event in the artifact directory so the monitor can access it
 69 	eventDir := filepath.Join(cfg.ArtifactDir, eventData.Name, jobID)
 70@@ -574,7 +585,8 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
 71 
 72 	manifest, err := eng.FindManifest()
 73 	if err != nil {
 74-		fmt.Fprintf(os.Stdout, "āŒ %s\n\n", err)
 75+		fmt.Fprintf(os.Stdout, "āŒ %s\n\n", err) //nolint:errcheck
 76+		//nolint:errcheck
 77 		fmt.Fprint(os.Stdout, `Create a pico.sh script in your workspace root:
 78 
 79   #!/usr/bin/env bash
 80@@ -601,14 +613,14 @@ See: https://github.com/picosh/pici
 81 `)
 82 		return err
 83 	}
 84-	fmt.Fprintf(os.Stdout, "šŸ” found %s\n", manifest)
 85+	fmt.Fprintf(os.Stdout, "šŸ” found %s\n", manifest) //nolint:errcheck
 86 
 87-	fmt.Fprint(os.Stdout, "šŸƒ launching sessions...\n")
 88+	fmt.Fprint(os.Stdout, "šŸƒ launching sessions...\n") //nolint:errcheck
 89 	if err := eng.Run(manifest); err != nil {
 90 		return fmt.Errorf("run: %w", err)
 91 	}
 92 
 93-	fmt.Fprintln(os.Stdout, "āœ… job launched")
 94+	fmt.Fprintln(os.Stdout, "āœ… job launched") //nolint:errcheck
 95 
 96 	if cfg.Wait {
 97 		if err := waitAndReport(cfg, log, eventData.Name, jobID); err != nil {
 98@@ -618,9 +630,9 @@ See: https://github.com/picosh/pici
 99 	}
100 
101 	session := fmt.Sprintf("ci.%s.%s.runner", eventData.Name, jobID)
102-	fmt.Fprintf(os.Stdout, "   zmx tail %s\n", session)
103-	fmt.Fprintf(os.Stdout, "   zmx history %s\n", session)
104-	fmt.Fprintf(os.Stdout, "   zmx attach %s\n", session)
105+	fmt.Fprintf(os.Stdout, "   zmx tail %s\n", session)    //nolint:errcheck
106+	fmt.Fprintf(os.Stdout, "   zmx history %s\n", session) //nolint:errcheck
107+	fmt.Fprintf(os.Stdout, "   zmx attach %s\n", session)  //nolint:errcheck
108 	return nil
109 }
110 
111@@ -636,8 +648,8 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
112 	signal.Notify(sigCh, syscall.SIGINT)
113 	defer signal.Stop(sigCh)
114 
115-	fmt.Fprintln(os.Stdout)
116-	fmt.Fprint(os.Stdout, "ā³ waiting for completion...\n")
117+	fmt.Fprintln(os.Stdout)                                //nolint:errcheck
118+	fmt.Fprint(os.Stdout, "ā³ waiting for completion...\n") //nolint:errcheck
119 
120 	// Track state for each session
121 	known := make(map[string]*sessionState)
122@@ -650,7 +662,7 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
123 		case <-cfg.Ctx.Done():
124 			return cfg.Ctx.Err()
125 		case <-sigCh:
126-			fmt.Fprintln(os.Stdout, "\nā¹ cancelled")
127+			fmt.Fprintln(os.Stdout, "\nā¹ cancelled") //nolint:errcheck
128 			return nil
129 		case <-ticker.C:
130 		}
131@@ -717,21 +729,21 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
132 
133 		// Overwrite previous lines with cursor-up, or print fresh
134 		if len(liveLines) > 0 {
135-			// Move cursor up to overwrite previous lines
136+			// Move cursor up to overwrite previous lines //nolint:errcheck
137 			for range len(liveLines) {
138-				fmt.Fprint(os.Stdout, "\033[A")
139+				fmt.Fprint(os.Stdout, "\033[A") //nolint:errcheck
140 			}
141 			// Clear each line
142-			for i, line := range lines {
143+			for i, line := range lines { //nolint:errcheck
144 				if i > 0 {
145-					fmt.Fprint(os.Stdout, "\n")
146+					fmt.Fprint(os.Stdout, "\n") //nolint:errcheck
147 				}
148-				fmt.Fprint(os.Stdout, line+"\033[K")
149+				fmt.Fprint(os.Stdout, line+"\033[K") //nolint:errcheck
150 			}
151-			fmt.Fprint(os.Stdout, "\n")
152-		} else {
153+			fmt.Fprint(os.Stdout, "\n") //nolint:errcheck
154+		} else { //nolint:errcheck
155 			for _, line := range lines {
156-				fmt.Fprintln(os.Stdout, line)
157+				fmt.Fprintln(os.Stdout, line) //nolint:errcheck
158 			}
159 		}
160 		liveLines = lines
161@@ -743,7 +755,7 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
162 	}
163 
164 	// Print last 25 lines of history for failed sessions only
165-	fmt.Fprintln(os.Stdout)
166+	fmt.Fprintln(os.Stdout) //nolint:errcheck
167 	for _, s := range jobSessions {
168 		state := known[s.Short]
169 		if state == nil || state.status != "failed" {
170@@ -751,28 +763,28 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
171 		}
172 
173 		separator := strings.Repeat("\u2500", 50)
174-		fmt.Fprintln(os.Stdout, separator)
175-		fmt.Fprintf(os.Stdout, "Session: %s (exit %s)\n", s.Short, state.exitCode)
176-		fmt.Fprintln(os.Stdout, separator)
177-		fmt.Fprintln(os.Stdout)
178+		fmt.Fprintln(os.Stdout, separator)                                         //nolint:errcheck
179+		fmt.Fprintf(os.Stdout, "Session: %s (exit %s)\n", s.Short, state.exitCode) //nolint:errcheck
180+		fmt.Fprintln(os.Stdout, separator)                                         //nolint:errcheck
181+		fmt.Fprintln(os.Stdout)                                                    //nolint:errcheck
182 
183 		history, err := fetchHistoryPlain(s.Name)
184 		if err != nil {
185-			fmt.Fprintf(os.Stdout, "   (history unavailable: %v)\n", err)
186+			fmt.Fprintf(os.Stdout, "   (history unavailable: %v)\n", err) //nolint:errcheck
187 		} else {
188 			lines := strings.Split(history, "\n")
189 			// Show last 25 lines
190 			if len(lines) > 25 {
191-				fmt.Fprintf(os.Stdout, "   ... (%d lines omitted)\n", len(lines)-25)
192+				fmt.Fprintf(os.Stdout, "   ... (%d lines omitted)\n", len(lines)-25) //nolint:errcheck
193 				lines = lines[len(lines)-25:]
194 			}
195 			for _, line := range lines {
196 				if line != "" {
197-					fmt.Fprintf(os.Stdout, "   %s\n", line)
198+					fmt.Fprintf(os.Stdout, "   %s\n", line) //nolint:errcheck
199 				}
200 			}
201 		}
202-		fmt.Fprintln(os.Stdout)
203+		fmt.Fprintln(os.Stdout) //nolint:errcheck
204 	}
205 
206 	// Final summary
207@@ -780,9 +792,9 @@ func waitAndReport(cfg *Cfg, log *slog.Logger, name, jobID string) error {
208 	_, _, duration := computeJobTiming(jobSessions)
209 	icon := map[string]string{"success": "āœ…", "failed": "āŒ"}[status]
210 	if exitCode == 0 {
211-		fmt.Fprintf(os.Stdout, "%s job finished: %s (%s)\n", icon, status, duration)
212+		fmt.Fprintf(os.Stdout, "%s job finished: %s (%s)\n", icon, status, duration) //nolint:errcheck
213 	} else {
214-		fmt.Fprintf(os.Stdout, "%s job failed: exit %d (%s)\n", icon, exitCode, duration)
215+		fmt.Fprintf(os.Stdout, "%s job failed: exit %d (%s)\n", icon, exitCode, duration) //nolint:errcheck
216 	}
217 
218 	return nil
219@@ -822,7 +834,6 @@ type monitorJobState struct {
220 	sessionOrder []string // insertion order for deterministic output
221 	sessions     map[string]*sessionState
222 	liveLines    []string // last set of status lines printed (for overwrite)
223-	published    bool     // final status already printed
224 }
225 
226 func runMonitor(cfg *Cfg) error {
227@@ -911,19 +922,19 @@ func renderJobRunning(output io.Writer, name, jobID string, group []SessionInfo,
228 	// Overwrite previous lines or print fresh
229 	if len(state.liveLines) > 0 {
230 		for range len(state.liveLines) {
231-			fmt.Fprint(output, "\033[A")
232+			fmt.Fprint(output, "\033[A") //nolint:errcheck
233 		}
234 		for i, line := range lines {
235 			if i > 0 {
236-				fmt.Fprint(output, "\n")
237+				fmt.Fprint(output, "\n") //nolint:errcheck
238 			}
239-			fmt.Fprint(output, line+"\033[K")
240+			fmt.Fprint(output, line+"\033[K") //nolint:errcheck
241 		}
242-		fmt.Fprint(output, "\n")
243+		fmt.Fprint(output, "\n") //nolint:errcheck
244 	} else {
245-		fmt.Fprintf(output, "   %-12s %s %s\n", name, "šŸš€", "running")
246+		fmt.Fprintf(output, "   %-12s %s %s\n", name, "šŸš€", "running") //nolint:errcheck
247 		for _, line := range lines {
248-			fmt.Fprintln(output, line)
249+			fmt.Fprintln(output, line) //nolint:errcheck
250 		}
251 	}
252 	state.liveLines = lines
253@@ -932,7 +943,7 @@ func renderJobRunning(output io.Writer, name, jobID string, group []SessionInfo,
254 // renderJobFinal prints the final status for a completed job.
255 func renderJobFinal(output io.Writer, name, jobID string, group []SessionInfo, duration, status string, success bool, workspace, artifactDir, artifactURL string) {
256 	icon := map[string]string{"success": "āœ…", "failed": "āŒ"}[status]
257-	fmt.Fprintf(output, "   %-12s %s %s (%s)\n", name, icon, status, duration)
258+	fmt.Fprintf(output, "   %-12s %s %s (%s)\n", name, icon, status, duration) //nolint:errcheck
259 
260 	// Per-session summary
261 	for _, s := range group {
262@@ -941,20 +952,20 @@ func renderJobFinal(output io.Writer, name, jobID string, group []SessionInfo, d
263 			icon = "āŒ"
264 		}
265 		dur := fmtDuration(s.Created, s.Ended)
266-		fmt.Fprintf(output, "   %-12s %s done (%s)\n", s.Short, icon, dur)
267+		fmt.Fprintf(output, "   %-12s %s done (%s)\n", s.Short, icon, dur) //nolint:errcheck
268 	}
269 
270 	// Context info
271-	fmt.Fprint(output, "\n")
272+	fmt.Fprint(output, "\n") //nolint:errcheck
273 	if workspace != "" {
274-		fmt.Fprintf(output, "   workspace:  %s\n", workspace)
275+		fmt.Fprintf(output, "   workspace:  %s\n", workspace) //nolint:errcheck
276 	}
277 	artifactPath := filepath.Join(artifactDir, name, jobID)
278-	fmt.Fprintf(output, "   artifacts:  %s\n", artifactPath)
279+	fmt.Fprintf(output, "   artifacts:  %s\n", artifactPath) //nolint:errcheck
280 	if artifactURL != "" {
281-		fmt.Fprintf(output, "   url:        %s\n", artifactURL)
282+		fmt.Fprintf(output, "   url:        %s\n", artifactURL) //nolint:errcheck
283 	}
284-	fmt.Fprint(output, "\n")
285+	fmt.Fprint(output, "\n") //nolint:errcheck
286 }
287 
288 func monitorTick(cfg *Cfg, log *slog.Logger, output io.Writer, jobStates map[string]*monitorJobState) error {
289@@ -1094,7 +1105,7 @@ func monitorTick(cfg *Cfg, log *slog.Logger, output io.Writer, jobStates map[str
290 
291 	// d. Sync artifacts once per tick
292 	if cfg.HumanOutput {
293-		fmt.Fprint(output, "   šŸ“¦ syncing artifacts...\n")
294+		fmt.Fprint(output, "   šŸ“¦ syncing artifacts...\n") //nolint:errcheck
295 	}
296 	if err := syncArtifacts(cfg, log); err != nil {
297 		log.Error("sync artifacts", "err", err)
298@@ -1389,16 +1400,6 @@ func allCompleted(sessions []SessionInfo) bool {
299 	return true
300 }
301 
302-func countCompleted(sessions []SessionInfo) int {
303-	count := 0
304-	for _, s := range sessions {
305-		if s.Ended != "" {
306-			count++
307-		}
308-	}
309-	return count
310-}
311-
312 func runCmd(cmd *exec.Cmd, log *slog.Logger) error {
313 	stdout, err := cmd.StdoutPipe()
314 	if err != nil {