feat(gate): auto-discover workflow run ID in gate check (bd-fbkd)
When bd gate check encounters a gh:run gate with a non-numeric await_id (workflow name hint like release.yml), it now auto-discovers the run ID: - Queries GitHub directly with gh run list --workflow=X - Takes the most recent run (deterministic: newest-first from API) - Updates the gates await_id and proceeds with status check This is ZFC-compliant: most recent run of workflow X is purely mechanical, no heuristic scoring. Agents no longer need to run bd gate discover manually. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
812f4e5297
commit
ee5bc5931a
@@ -638,7 +638,7 @@ type ghPRStatus struct {
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// isNumericID returns true if the string is a valid numeric ID
|
||||
// isNumericID returns true if the string contains only digits (a GitHub run ID)
|
||||
func isNumericID(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
@@ -651,20 +651,80 @@ func isNumericID(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// queryGitHubRunsForWorkflow queries recent runs for a specific workflow using gh CLI.
|
||||
// Returns runs sorted newest-first (GitHub API default).
|
||||
func queryGitHubRunsForWorkflow(workflow string, limit int) ([]GHWorkflowRun, error) {
|
||||
if _, err := exec.LookPath("gh"); err != nil {
|
||||
return nil, fmt.Errorf("gh CLI not found: install from https://cli.github.com")
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"run", "list",
|
||||
"--workflow", workflow,
|
||||
"--json", "databaseId,name,status,conclusion,createdAt,workflowName",
|
||||
"--limit", fmt.Sprintf("%d", limit),
|
||||
}
|
||||
|
||||
cmd := exec.Command("gh", args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return nil, fmt.Errorf("gh run list --workflow=%s failed: %s", workflow, string(exitErr.Stderr))
|
||||
}
|
||||
return nil, fmt.Errorf("gh run list: %w", err)
|
||||
}
|
||||
|
||||
var runs []GHWorkflowRun
|
||||
if err := json.Unmarshal(output, &runs); err != nil {
|
||||
return nil, fmt.Errorf("parse gh output: %w", err)
|
||||
}
|
||||
|
||||
return runs, nil
|
||||
}
|
||||
|
||||
// discoverRunIDByWorkflowName queries GitHub for the most recent run of a workflow.
|
||||
// Returns (runID, error). This is ZFC-compliant: "most recent run" is deterministic.
|
||||
func discoverRunIDByWorkflowName(workflowHint string) (string, error) {
|
||||
// Query GitHub directly for this workflow (efficient, avoids limit issues)
|
||||
runs, err := queryGitHubRunsForWorkflow(workflowHint, 5)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to query workflow runs: %w", err)
|
||||
}
|
||||
|
||||
if len(runs) == 0 {
|
||||
return "", fmt.Errorf("no runs found for workflow '%s'", workflowHint)
|
||||
}
|
||||
|
||||
// Take the most recent run (gh returns newest-first)
|
||||
// This is deterministic: "most recent" is a total ordering by creation time
|
||||
return fmt.Sprintf("%d", runs[0].DatabaseID), nil
|
||||
}
|
||||
|
||||
// checkGHRun checks a GitHub Actions workflow run gate
|
||||
func checkGHRun(gate *types.Issue) (resolved, escalated bool, reason string, err error) {
|
||||
if gate.AwaitID == "" {
|
||||
return false, false, "no run ID specified - run 'bd gate discover' first", nil
|
||||
return false, false, "no run ID specified - set await_id or use workflow name hint", nil
|
||||
}
|
||||
|
||||
// Check if AwaitID is a numeric run ID or a workflow name hint
|
||||
runID := gate.AwaitID
|
||||
|
||||
// If await_id is a workflow name hint (non-numeric), auto-discover the run ID
|
||||
if !isNumericID(gate.AwaitID) {
|
||||
// Non-numeric AwaitID is a workflow name hint, needs discovery
|
||||
return false, false, fmt.Sprintf("awaiting discovery (workflow hint: %s) - run 'bd gate discover'", gate.AwaitID), nil
|
||||
discoveredID, discoverErr := discoverRunIDByWorkflowName(gate.AwaitID)
|
||||
if discoverErr != nil {
|
||||
return false, false, fmt.Sprintf("workflow hint '%s': %v", gate.AwaitID, discoverErr), nil
|
||||
}
|
||||
|
||||
// Update the gate with the discovered run ID
|
||||
if updateErr := updateGateAwaitID(nil, gate.ID, discoveredID); updateErr != nil {
|
||||
return false, false, "", fmt.Errorf("failed to update gate with discovered run ID: %w", updateErr)
|
||||
}
|
||||
|
||||
runID = discoveredID
|
||||
}
|
||||
|
||||
// Run: gh run view <id> --json status,conclusion,name
|
||||
cmd := exec.Command("gh", "run", "view", gate.AwaitID, "--json", "status,conclusion,name") // #nosec G204 -- gate.AwaitID is a validated GitHub run ID
|
||||
cmd := exec.Command("gh", "run", "view", runID, "--json", "status,conclusion,name") // #nosec G204 -- runID is a validated GitHub run ID
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
Reference in New Issue
Block a user