From dbc6bbca1012dcf7d668f2fd4206c6c76ae8611f Mon Sep 17 00:00:00 2001 From: Chaz Dinkle Date: Mon, 13 Apr 2026 17:58:10 -0400 Subject: [PATCH 1/2] ci: use org-wide reusable Go CI workflow Calls cogos-dev/.github go-ci.yml for vet, build, test, and lint on push/PR to main. Go version pinned to 1.24 per go.mod. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d70d5a9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,12 @@ +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + go: + uses: cogos-dev/.github/.github/workflows/go-ci.yml@main + with: + go-version: '1.24' From fe393e82d9572ccf4e57baa87a34f885984c650c Mon Sep 17 00:00:00 2001 From: Chaz Dinkle Date: Mon, 13 Apr 2026 18:09:06 -0400 Subject: [PATCH 2/2] fix: resolve errcheck lint errors for CI compliance Handle all unchecked return values flagged by golangci-lint errcheck: - resp.Body.Close / f.Close in defers: wrap with _ = assignment - enc.Encode / dec.Decode: check error and handle (log or exit) - server.Shutdown: check and log error - fmt.Sscanf: discard return values explicitly - io.Copy: check error return Co-Authored-By: Claude Opus 4.6 (1M context) --- heartbeat.go | 4 ++-- identity.go | 2 +- node.go | 4 +++- protocol.go | 28 +++++++++++++++++++--------- run.go | 29 ++++++++++++++++++++--------- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/heartbeat.go b/heartbeat.go index 038ae3c..0719771 100644 --- a/heartbeat.go +++ b/heartbeat.go @@ -158,7 +158,7 @@ func sendHeartbeat(nodeName, addr string, hb *Heartbeat) { log.Printf("[%s] Heartbeat to %s failed: %v", nodeName, addr, err) return } - resp.Body.Close() + _ = resp.Body.Close() } func sendJoinRequest(node *Node, addr string) error { @@ -185,7 +185,7 @@ func sendJoinRequest(node *Node, addr string) error { if err != nil { return err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("join request returned %d", resp.StatusCode) diff --git a/identity.go b/identity.go index 2804cd1..8b6d86f 100644 --- a/identity.go +++ b/identity.go @@ -49,7 +49,7 @@ func SaveIdentity(id *NodeIdentity, dir string) error { if err != nil { return fmt.Errorf("create key file: %w", err) } - defer f.Close() + defer func() { _ = f.Close() }() return pem.Encode(f, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}) } diff --git a/node.go b/node.go index 52ced34..3fb07f4 100644 --- a/node.go +++ b/node.go @@ -132,7 +132,9 @@ func (n *Node) Stop() { if n.server != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - n.server.Shutdown(ctx) + if err := n.server.Shutdown(ctx); err != nil { + log.Printf("[%s] Shutdown error: %v", n.Name, err) + } } } diff --git a/protocol.go b/protocol.go index 269c193..006b6a7 100644 --- a/protocol.go +++ b/protocol.go @@ -98,7 +98,9 @@ func handlePeers(node *Node) http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(node.Peers.Summarize()) + if err := json.NewEncoder(w).Encode(node.Peers.Summarize()); err != nil { + log.Printf("[%s] Failed to encode peers response: %v", node.Name, err) + } } } @@ -125,10 +127,12 @@ func handleChallenge(node *Node) http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "events": events, "coherence": ValidateCoherence(events), - }) + }); err != nil { + log.Printf("[%s] Failed to encode challenge response: %v", node.Name, err) + } } } @@ -171,10 +175,12 @@ func handleJoin(node *Node) http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "status": "accepted", "peers": peerAddrs, - }) + }); err != nil { + log.Printf("[%s] Failed to encode join response: %v", node.Name, err) + } } } @@ -195,7 +201,9 @@ func handleHealth(node *Node) http.HandlerFunc { if !report.Pass { w.WriteHeader(http.StatusServiceUnavailable) } - json.NewEncoder(w).Encode(report) + if err := json.NewEncoder(w).Encode(report); err != nil { + log.Printf("[%s] Failed to encode health response: %v", node.Name, err) + } } } @@ -213,10 +221,12 @@ func handleState(node *Node) http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "state": state, "peers": node.Peers.Summarize(), - }) + }); err != nil { + log.Printf("[%s] Failed to encode state response: %v", node.Name, err) + } } } @@ -245,7 +255,7 @@ func issueChallenge(node *Node, peer *PeerState) { log.Printf("[%s] Challenge to %s failed: %v", node.Name, FormatNodeID(peer.NodeID), err) return } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() var result struct { Events []*EventEnvelope `json:"events"` diff --git a/run.go b/run.go index af32579..f67cdab 100644 --- a/run.go +++ b/run.go @@ -69,7 +69,7 @@ func cmdNode() { name = args[i] case "--port": i++ - fmt.Sscanf(args[i], "%d", &port) + _, _ = fmt.Sscanf(args[i], "%d", &port) case "--peers": i++ peersStr = args[i] @@ -145,8 +145,10 @@ func cmdInject() { fmt.Fprintf(os.Stderr, "inject failed: %v\n", err) os.Exit(1) } - defer resp.Body.Close() - io.Copy(os.Stdout, resp.Body) + defer func() { _ = resp.Body.Close() }() + if _, err := io.Copy(os.Stdout, resp.Body); err != nil { + fmt.Fprintf(os.Stderr, "read response failed: %v\n", err) + } fmt.Println() } @@ -173,7 +175,7 @@ func cmdTamper() { fmt.Fprintf(os.Stderr, "get state failed: %v\n", err) os.Exit(1) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() var state struct { State struct { @@ -213,10 +215,13 @@ func cmdStatus() { fmt.Fprintf(os.Stderr, "get state failed: %v\n", err) os.Exit(1) } - defer stateResp.Body.Close() + defer func() { _ = stateResp.Body.Close() }() var stateData json.RawMessage - json.NewDecoder(stateResp.Body).Decode(&stateData) + if err := json.NewDecoder(stateResp.Body).Decode(&stateData); err != nil { + fmt.Fprintf(os.Stderr, "decode state failed: %v\n", err) + os.Exit(1) + } // Get health. healthResp, err := client.Get(fmt.Sprintf("%s/health", target)) @@ -224,10 +229,13 @@ func cmdStatus() { fmt.Fprintf(os.Stderr, "get health failed: %v\n", err) os.Exit(1) } - defer healthResp.Body.Close() + defer func() { _ = healthResp.Body.Close() }() var healthData json.RawMessage - json.NewDecoder(healthResp.Body).Decode(&healthData) + if err := json.NewDecoder(healthResp.Body).Decode(&healthData); err != nil { + fmt.Fprintf(os.Stderr, "decode health failed: %v\n", err) + os.Exit(1) + } output := map[string]json.RawMessage{ "state": stateData, @@ -236,5 +244,8 @@ func cmdStatus() { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") - enc.Encode(output) + if err := enc.Encode(output); err != nil { + fmt.Fprintf(os.Stderr, "encode output failed: %v\n", err) + os.Exit(1) + } }