Skip to content

Commit 04bded2

Browse files
michaelmcneesclaude
andcommitted
fix: preorder tree display, audit before commit, deeper cancellation checks (PR review)
- status.go: Use text path in recursive CTE for correct preorder traversal - cancel.go: Emit audit events inside transaction via EmitTx before commit - engine.go: Check cancellation status before each retry attempt - engine.go: Guard updateStep against overwriting cancelled status - workflow_connector_test.go: Assert specific depth error substring Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 594c72f commit 04bded2

4 files changed

Lines changed: 29 additions & 13 deletions

File tree

packages/engine/internal/cli/cancel.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,20 @@ func newCancelCommand() *cobra.Command {
9797
return fmt.Errorf("cancelling step executions: %w", err)
9898
}
9999

100-
if err := tx.Commit(); err != nil {
101-
return fmt.Errorf("committing cancellation: %w", err)
102-
}
103-
104-
// Emit audit events for each cancelled execution.
105-
auditor := &audit.PostgresEmitter{DB: database}
100+
// Emit audit events inside the transaction so they commit atomically.
106101
for _, id := range cancelledIDs {
107-
_ = auditor.Emit(cmd.Context(), audit.Event{
102+
if err := audit.EmitTx(cmd.Context(), tx, audit.Event{
108103
Timestamp: time.Now(),
109104
Actor: "cli",
110105
Action: audit.ActionExecutionCancelled,
111106
Resource: audit.Resource{Type: "workflow_execution", ID: id},
112-
})
107+
}); err != nil {
108+
return fmt.Errorf("emitting audit event for %s: %w", id, err)
109+
}
110+
}
111+
112+
if err := tx.Commit(); err != nil {
113+
return fmt.Errorf("committing cancellation: %w", err)
113114
}
114115

115116
fmt.Fprintf(cmd.OutOrStdout(), "Cancelled execution %s (%d executions affected)\n", execID, len(cancelledIDs))

packages/engine/internal/cli/status.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,19 @@ func newStatusCommand() *cobra.Command {
101101
}
102102

103103
// Query for full descendant execution tree using recursive CTE.
104+
// Build a text path for correct preorder traversal ordering of nested trees.
104105
childRows, err := database.QueryContext(cmd.Context(),
105106
`WITH RECURSIVE tree AS (
106-
SELECT id, workflow_name, workflow_version, status, 1 as depth
107+
SELECT id, workflow_name, workflow_version, status, 1 as depth,
108+
id::text as path
107109
FROM workflow_executions WHERE parent_execution_id = $1
108110
UNION ALL
109-
SELECT e.id, e.workflow_name, e.workflow_version, e.status, t.depth + 1
111+
SELECT e.id, e.workflow_name, e.workflow_version, e.status, t.depth + 1,
112+
t.path || '/' || e.id::text
110113
FROM workflow_executions e
111114
JOIN tree t ON e.parent_execution_id = t.id
112115
)
113-
SELECT id, workflow_name, workflow_version, status, depth FROM tree ORDER BY depth, id`, execID,
116+
SELECT id, workflow_name, workflow_version, status, depth, path FROM tree ORDER BY path`, execID,
114117
)
115118
if err == nil {
116119
defer childRows.Close()
@@ -121,11 +124,12 @@ func newStatusCommand() *cobra.Command {
121124
Version int
122125
Status string
123126
Depth int
127+
Path string
124128
}
125129
var children []childExec
126130
for childRows.Next() {
127131
var c childExec
128-
if err := childRows.Scan(&c.ID, &c.Workflow, &c.Version, &c.Status, &c.Depth); err == nil {
132+
if err := childRows.Scan(&c.ID, &c.Workflow, &c.Version, &c.Status, &c.Depth, &c.Path); err == nil {
129133
children = append(children, c)
130134
}
131135
}

packages/engine/internal/engine/engine.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,12 @@ func (e *Engine) executeStepLogic(ctx context.Context, execID string, step workf
622622
var lastErr error
623623

624624
for attempt := 1; attempt <= maxAttempts; attempt++ {
625+
// Check if the execution has been cancelled before each retry attempt.
626+
var execStatus string
627+
if err := e.DB.QueryRowContext(ctx, "SELECT status FROM workflow_executions WHERE id = $1", execID).Scan(&execStatus); err == nil && execStatus == "cancelled" {
628+
return nil, fmt.Errorf("execution cancelled")
629+
}
630+
625631
// Create a fresh artifacts scratch dir per attempt.
626632
if len(step.Artifacts) > 0 && e.Storage != nil {
627633
if artifactsDir != "" {
@@ -1055,9 +1061,10 @@ func (e *Engine) updateStep(ctx context.Context, execID, stepName, status string
10551061
completedAt = time.Now()
10561062
}
10571063

1064+
// Don't overwrite a cancelled step.
10581065
_, err = e.DB.ExecContext(ctx,
10591066
`UPDATE step_executions SET status = $1, output = $2, error = $3, completed_at = $4, continue_on_error = $5, updated_at = NOW()
1060-
WHERE execution_id = $6 AND step_name = $7 AND attempt = 1`,
1067+
WHERE execution_id = $6 AND step_name = $7 AND attempt = 1 AND status != 'cancelled'`,
10611068
status, outputJSON, errorVal, completedAt, continueOnError, execID, stepName,
10621069
)
10631070
if err != nil {

packages/engine/internal/engine/workflow_connector_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package engine
33
import (
44
"context"
55
"encoding/json"
6+
"strings"
67
"testing"
78
)
89

@@ -227,6 +228,9 @@ steps:
227228
if callDeep.Error == "" {
228229
t.Error("call-deep error should be non-empty")
229230
}
231+
if !strings.Contains(callDeep.Error, "depth") {
232+
t.Errorf("expected depth limit error, got: %s", callDeep.Error)
233+
}
230234
t.Logf("depth limit error: %s", callDeep.Error)
231235
}
232236

0 commit comments

Comments
 (0)