Open
Conversation
Resolves DEP-3881. The positional arg now accepts a workflow ID (e.g. from the /workflows/<id> URL). When the ID doesn't match a run or job, the CLI lists recent runs and searches for a matching workflow, then auto-filters to that workflow's jobs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Workflow search adds major latency to attempt ID path
- Reordered fallback logic to try the attempt ID path (single API call) before the expensive workflow search (up to 51 API calls), reducing latency from ~53 to 2 API calls for attempt ID lookups.
Or push these changes by commenting:
@cursor push d143c7461f
Preview (d143c7461f)
diff --git a/pkg/cmd/ci/logs.go b/pkg/cmd/ci/logs.go
--- a/pkg/cmd/ci/logs.go
+++ b/pkg/cmd/ci/logs.go
@@ -88,6 +88,20 @@
return printLogs(ctx, tokenVal, orgID, attemptID)
}
+ // If --job or --workflow flags are NOT specified, try attempt ID
+ // early (single API call) before the expensive workflow search.
+ var attemptErr error
+ if job == "" && workflow == "" {
+ lines, err := api.CIGetJobAttemptLogs(ctx, tokenVal, orgID, id)
+ if err == nil {
+ for _, line := range lines {
+ fmt.Println(line.Body)
+ }
+ return nil
+ }
+ attemptErr = err
+ }
+
// Try resolving as a workflow ID by searching recent runs.
resp, wfPath, wfErr := resolveWorkflow(ctx, tokenVal, orgID, id)
if wfErr == nil {
@@ -104,24 +118,13 @@
return printLogs(ctx, tokenVal, orgID, attemptID)
}
- // Fall back to treating the ID as an attempt ID directly.
- // Don't fall back if --job or --workflow were specified — those
- // only make sense for run-level resolution.
+ // All paths failed — show errors so the user can
+ // distinguish "bad ID" from "auth/network failure".
if job != "" || workflow != "" {
return fmt.Errorf("failed to look up run: %w\n as workflow: %v", runErr, wfErr)
}
- lines, err := api.CIGetJobAttemptLogs(ctx, tokenVal, orgID, id)
- if err != nil {
- // All paths failed — show errors so the user can
- // distinguish "bad ID" from "auth/network failure".
- return fmt.Errorf("could not resolve %q as a run, job, workflow, or attempt ID:\n as run: %v\n as workflow: %v\n as attempt: %v", id, runErr, wfErr, err)
- }
-
- for _, line := range lines {
- fmt.Println(line.Body)
- }
- return nil
+ return fmt.Errorf("could not resolve %q as a run, job, workflow, or attempt ID:\n as run: %v\n as workflow: %v\n as attempt: %v", id, runErr, wfErr, attemptErr)
},
}Move the workflow ID resolution (which lists up to 50 runs) after the attempt ID fallback so that direct attempt IDs resolve in ~2 calls instead of ~51. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Workflow auto-filter fails for inline workflows with empty path
- Added a separate workflowIDFilter parameter to findLogsJob and resolveAttempt that filters by workflow ID when the path is empty, allowing inline workflows to be properly filtered.
Or push these changes by commenting:
@cursor push b67b3dfbe2
Preview (b67b3dfbe2)
diff --git a/pkg/cmd/ci/logs.go b/pkg/cmd/ci/logs.go
--- a/pkg/cmd/ci/logs.go
+++ b/pkg/cmd/ci/logs.go
@@ -71,16 +71,18 @@
// If the positional arg matches a workflow ID in the response,
// auto-filter to that workflow's jobs.
wfFilter := workflow
+ wfIDFilter := ""
if wfFilter == "" {
for _, wf := range resp.Workflows {
if wf.WorkflowId == id {
wfFilter = wf.WorkflowPath
+ wfIDFilter = id // Use workflow ID for inline workflows with empty path
break
}
}
}
- attemptID, err := resolveAttempt(resp, id, job, wfFilter)
+ attemptID, err := resolveAttempt(resp, id, job, wfFilter, wfIDFilter)
if err != nil {
return err
}
@@ -107,11 +109,13 @@
resp, wfPath, wfErr := resolveWorkflow(ctx, tokenVal, orgID, id)
if wfErr == nil {
wfFilter := workflow
+ wfIDFilter := ""
if wfFilter == "" {
wfFilter = wfPath
+ wfIDFilter = id // Use workflow ID for inline workflows with empty path
}
- attemptID, err := resolveAttempt(resp, id, job, wfFilter)
+ attemptID, err := resolveAttempt(resp, id, job, wfFilter, wfIDFilter)
if err != nil {
return err
}
@@ -171,8 +175,8 @@
// resolveAttempt finds the target attempt from a run status response.
// It selects a job (by --job flag, by job ID match, or auto-select), then
// picks the latest attempt and prints informational messages about what was chosen.
-func resolveAttempt(resp *civ1.GetRunStatusResponse, originalID, jobKey, workflowFilter string) (string, error) {
- targetJob, workflowPath, err := findLogsJob(resp, originalID, jobKey, workflowFilter)
+func resolveAttempt(resp *civ1.GetRunStatusResponse, originalID, jobKey, workflowFilter, workflowIDFilter string) (string, error) {
+ targetJob, workflowPath, err := findLogsJob(resp, originalID, jobKey, workflowFilter, workflowIDFilter)
if err != nil {
return "", err
}
@@ -217,12 +221,16 @@
// findLogsJob locates the target job in the run status response.
// Returns the job and the workflow path it belongs to.
-func findLogsJob(resp *civ1.GetRunStatusResponse, originalID, jobKey, workflowFilter string) (*civ1.JobStatus, string, error) {
+func findLogsJob(resp *civ1.GetRunStatusResponse, originalID, jobKey, workflowFilter, workflowIDFilter string) (*civ1.JobStatus, string, error) {
var candidates []jobCandidate
for _, wf := range resp.Workflows {
if workflowFilter != "" && !workflowPathMatches(wf.WorkflowPath, workflowFilter) {
continue
}
+ // Filter by workflow ID for inline workflows (which have empty paths)
+ if workflowIDFilter != "" && wf.WorkflowId != workflowIDFilter {
+ continue
+ }
for _, j := range wf.Jobs {
candidates = append(candidates, jobCandidate{
job: j,
diff --git a/pkg/cmd/ci/logs_test.go b/pkg/cmd/ci/logs_test.go
--- a/pkg/cmd/ci/logs_test.go
+++ b/pkg/cmd/ci/logs_test.go
@@ -19,7 +19,7 @@
},
}
- job, path, err := findLogsJob(resp, "run-1", "", "")
+ job, path, err := findLogsJob(resp, "run-1", "", "", "")
if err != nil {
t.Fatal(err)
}
@@ -45,7 +45,7 @@
},
}
- _, _, err := findLogsJob(resp, "run-1", "", "")
+ _, _, err := findLogsJob(resp, "run-1", "", "", "")
if err == nil {
t.Fatal("expected error for multiple jobs without --job flag")
}
@@ -65,7 +65,7 @@
},
}
- job, _, err := findLogsJob(resp, "run-1", "test", "")
+ job, _, err := findLogsJob(resp, "run-1", "test", "", "")
if err != nil {
t.Fatal(err)
}
@@ -88,7 +88,7 @@
},
}
- job, _, err := findLogsJob(resp, "job-2", "", "")
+ job, _, err := findLogsJob(resp, "job-2", "", "", "")
if err != nil {
t.Fatal(err)
}
@@ -116,7 +116,7 @@
},
}
- _, _, err := findLogsJob(resp, "run-1", "build", "")
+ _, _, err := findLogsJob(resp, "run-1", "build", "", "")
if err == nil {
t.Fatal("expected error for duplicate job key without --workflow")
}
@@ -141,7 +141,7 @@
},
}
- job, path, err := findLogsJob(resp, "run-1", "build", "ci.yml")
+ job, path, err := findLogsJob(resp, "run-1", "build", "ci.yml", "")
if err != nil {
t.Fatal(err)
}
@@ -166,7 +166,7 @@
},
}
- _, _, err := findLogsJob(resp, "run-1", "", "release.yml")
+ _, _, err := findLogsJob(resp, "run-1", "", "release.yml", "")
if err == nil {
t.Fatal("expected error for non-matching workflow filter")
}
@@ -193,7 +193,7 @@
},
}
- attemptID, err := resolveAttempt(resp, "run-1", "build", "")
+ attemptID, err := resolveAttempt(resp, "run-1", "build", "", "")
if err != nil {
t.Fatal(err)
}
@@ -215,7 +215,7 @@
},
}
- _, err := resolveAttempt(resp, "run-1", "", "")
+ _, err := resolveAttempt(resp, "run-1", "", "", "")
if err == nil {
t.Fatal("expected error for job with no attempts")
}
@@ -235,7 +235,7 @@
},
}
- job, _, err := findLogsJob(resp, "run-1", "test", "")
+ job, _, err := findLogsJob(resp, "run-1", "test", "", "")
if err != nil {
t.Fatal(err)
}
@@ -263,7 +263,7 @@
},
}
- _, _, err := findLogsJob(resp, "run-1", "build", "")
+ _, _, err := findLogsJob(resp, "run-1", "build", "", "")
if err == nil {
t.Fatal("expected error for ambiguous suffix match across workflows")
}
@@ -345,7 +345,7 @@
}
// Filtering by the first workflow's path should only see its jobs.
- job, path, err := findLogsJob(resp, "wf-1", "", ".depot/workflows/ci.yml")
+ job, path, err := findLogsJob(resp, "wf-1", "", ".depot/workflows/ci.yml", "")
if err != nil {
t.Fatal(err)
}
@@ -393,7 +393,7 @@
}
// Passing workflow path as the filter should auto-select the single job in that workflow.
- attemptID, err := resolveAttempt(resp, "wf-1", "", ".depot/workflows/ci.yml")
+ attemptID, err := resolveAttempt(resp, "wf-1", "", ".depot/workflows/ci.yml", "")
if err != nil {
t.Fatal(err)
}
@@ -402,6 +402,42 @@
}
}
+func TestFindLogsJob_InlineWorkflowIDFilter(t *testing.T) {
+ // Test that inline workflows (with empty paths) can be filtered by workflow ID.
+ resp := &civ1.GetRunStatusResponse{
+ RunId: "run-1",
+ Workflows: []*civ1.WorkflowStatus{
+ {
+ WorkflowId: "wf-1",
+ WorkflowPath: "", // Inline workflow has empty path
+ Name: "inline-workflow",
+ Jobs: []*civ1.JobStatus{
+ {JobId: "job-1", JobKey: "build", Status: "finished"},
+ },
+ },
+ {
+ WorkflowId: "wf-2",
+ WorkflowPath: ".depot/workflows/release.yml",
+ Jobs: []*civ1.JobStatus{
+ {JobId: "job-2", JobKey: "deploy", Status: "running"},
+ },
+ },
+ },
+ }
+
+ // Filter by workflow ID should only see the inline workflow's jobs.
+ job, path, err := findLogsJob(resp, "wf-1", "", "", "wf-1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if job.JobId != "job-1" {
+ t.Fatalf("expected job ID %q, got %q", "job-1", job.JobId)
+ }
+ if path != "" {
+ t.Fatalf("expected empty workflow path for inline workflow, got %q", path)
+ }
+}
+
func TestWorkflowPathMatches(t *testing.T) {
tests := []struct {
path stringWhen a workflow has no path (inline workflows), fall back to using the workflow ID as the filter value, and match against WorkflowId in findLogsJob so filtering still works. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121watts
approved these changes
Mar 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
depot ci logs <workflow-id>now resolves a workflow ID (from the/workflows/<id>URL) by searching recent runs for a matching workflowCloses DEP-3881
Test plan
findLogsJobandresolveAttemptdepot ci logs n1bf3p63c1 --org cl0wyyk6k39487ebgraxasinja🤖 Generated with Claude Code
Note
Medium Risk
Adds a new resolution path that lists recent runs and fetches run status to match workflow IDs, increasing API call volume/latency and introducing new error-handling paths for log retrieval.
Overview
depot ci logsnow accepts a workflow ID as the positional argument and will resolve it to the latest job attempt by searching recent runs and selecting jobs within the matched workflow.The command also auto-filters jobs when the provided ID matches a workflow in a run status response, refactors log printing into
printLogs, and improves failure output to surface workflow-resolution errors alongside run/attempt lookup failures.Written by Cursor Bugbot for commit a69a1ca. This will update automatically on new commits. Configure here.