Skip to content

Commit 6e8debb

Browse files
Frank Guoclaude
andcommitted
fix: report errors instead of silent exit in push and checkpoint
- Push now reports when no remote is configured, no data exists, or no new checkpoints to export instead of silently exiting 0 - Checkpoint validates DB health on open and surfaces checkpoint_state query errors instead of discarding them Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a928aa2 commit 6e8debb

3 files changed

Lines changed: 33 additions & 11 deletions

File tree

cmd/rekal/cli/checkpoint.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ func runCheckpoint(cmd *cobra.Command, gitRoot string) error {
6363
}
6464
defer dataDB.Close()
6565

66+
// Verify DB is healthy by running a simple query.
67+
if _, err := dataDB.Exec("SELECT 1"); err != nil {
68+
return fmt.Errorf("data DB is corrupt or unreadable: %w", err)
69+
}
70+
6671
email := gitConfigValue("user.email")
6772
entropy := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
6873
newID := func() string {
@@ -90,7 +95,10 @@ func runCheckpoint(cmd *cobra.Command, gitRoot string) error {
9095
hash := sha256Hex(data)
9196

9297
// Check cached state — skip if size + hash match.
93-
cachedSize, cachedHash, found, _ := db.GetCheckpointState(dataDB, f)
98+
cachedSize, cachedHash, found, csErr := db.GetCheckpointState(dataDB, f)
99+
if csErr != nil {
100+
return fmt.Errorf("check checkpoint state: %w", csErr)
101+
}
94102
if found && cachedSize == info.Size() && cachedHash == hash {
95103
continue
96104
}

cmd/rekal/cli/integration_test/checkpoint_e2e_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ func TestPush_NoBranch_Silent(t *testing.T) {
459459
if err != nil {
460460
t.Fatalf("push (no branch): %v", err)
461461
}
462-
if stderr != "" {
463-
t.Errorf("push with no branch should be silent, got: %q", stderr)
462+
if !strings.Contains(stderr, "no data to push") {
463+
t.Errorf("push with no branch should report no data, got: %q", stderr)
464464
}
465465
}
466466

@@ -472,8 +472,8 @@ func TestPush_NoRemote_Silent(t *testing.T) {
472472
if err != nil {
473473
t.Fatalf("push (no remote): %v", err)
474474
}
475-
if strings.Contains(stderr, "pushed to origin/") {
476-
t.Errorf("push with no remote should not report success, got: %q", stderr)
475+
if !strings.Contains(stderr, "no remote") {
476+
t.Errorf("push with no remote should report it, got: %q", stderr)
477477
}
478478
}
479479

@@ -665,12 +665,15 @@ func TestPush_NoNewCheckpoints(t *testing.T) {
665665
t.Fatalf("git remote add: %v", err)
666666
}
667667

668-
// Push with no checkpoints — should still push the orphan branch (initial empty body).
668+
// Push with no checkpoints — should report no new checkpoints but still push initial branch.
669669
_, stderr, err := env.RunCLI("push")
670670
if err != nil {
671671
t.Fatalf("push (no checkpoints): %v", err)
672672
}
673-
// Should push the initial orphan branch.
673+
if !strings.Contains(stderr, "no new checkpoints") {
674+
t.Errorf("expected 'no new checkpoints' message, got: %q", stderr)
675+
}
676+
// Should still push the initial orphan branch.
674677
if !strings.Contains(stderr, "pushed to origin/") {
675678
t.Errorf("expected push of initial branch, got: %q", stderr)
676679
}

cmd/rekal/cli/push.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ func runPush(cmd *cobra.Command, gitRoot string, force bool) error {
4040

4141
// Check if local branch exists — if not, nothing to push.
4242
if err := exec.Command("git", "-C", gitRoot, "rev-parse", "--verify", branch).Run(); err != nil {
43+
fmt.Fprintln(cmd.ErrOrStderr(), "rekal: no data to push (run 'rekal checkpoint' first)")
44+
return nil
45+
}
46+
47+
// Check if remote is configured.
48+
if err := exec.Command("git", "-C", gitRoot, "remote", "get-url", "origin").Run(); err != nil {
49+
fmt.Fprintln(cmd.ErrOrStderr(), "rekal: no remote 'origin' configured — skipping push")
4350
return nil
4451
}
4552

@@ -52,6 +59,8 @@ func runPush(cmd *cobra.Command, gitRoot string, force bool) error {
5259
if _, err := commitWireFormat(gitRoot, body, dict); err != nil {
5360
return fmt.Errorf("commit to rekal branch: %w", err)
5461
}
62+
} else {
63+
fmt.Fprintln(cmd.ErrOrStderr(), "rekal: no new checkpoints to export")
5564
}
5665

5766
// Compare local SHA vs remote tracking SHA — skip if identical.
@@ -61,14 +70,16 @@ func runPush(cmd *cobra.Command, gitRoot string, force bool) error {
6170
}
6271
remoteSHA, err := exec.Command("git", "-C", gitRoot, "rev-parse", "origin/"+branch).Output()
6372
if err == nil && strings.TrimSpace(string(localSHA)) == strings.TrimSpace(string(remoteSHA)) {
64-
return nil // already up to date
73+
fmt.Fprintln(cmd.ErrOrStderr(), "rekal: already up to date")
74+
return nil
6575
}
6676

6777
if force {
6878
forceCmd := exec.Command("git", "-C", gitRoot, "push", "--no-verify", "--force", "origin", branch)
6979
forceCmd.Stdin = nil
70-
if err := forceCmd.Run(); err != nil {
71-
return nil // network or other error — fail silently
80+
if output, err := forceCmd.CombinedOutput(); err != nil {
81+
fmt.Fprintf(cmd.ErrOrStderr(), "rekal: force push failed: %s\n", strings.TrimSpace(string(output)))
82+
return nil
7283
}
7384
fmt.Fprintf(cmd.ErrOrStderr(), "rekal: force pushed to origin/%s\n", branch)
7485
return nil
@@ -84,7 +95,7 @@ func runPush(cmd *cobra.Command, gitRoot string, force bool) error {
8495
fmt.Fprintln(cmd.ErrOrStderr(), "rekal: your remote branch has diverged from local — review and run 'rekal push --force' to overwrite remote with local data")
8596
return nil
8697
}
87-
// Other errors (network, no remote) — return silently.
98+
fmt.Fprintf(cmd.ErrOrStderr(), "rekal: push failed: %s\n", strings.TrimSpace(string(output)))
8899
return nil
89100
}
90101

0 commit comments

Comments
 (0)