Commit f175240eb2ef

Vincent Demeester <vincent@sbr.pm>
2026-02-13 22:50:26
feat(pi): enhanced github extension checks-log to accept PR numbers
Improved the checks-log action to accept either runId or PR number. When a PR number is provided, the extension automatically finds the first failed workflow run and fetches its logs, eliminating the need for manual run ID lookup. Enhanced LLM usability by allowing natural workflow of checking logs directly from PR numbers instead of requiring separate steps to find run IDs.
1 parent fd58871
Changed files (2)
dots
pi
agent
extensions
github
dots/pi/agent/extensions/github/actions/checks.ts
@@ -76,6 +76,7 @@ export async function handleChecks(
 
 /**
  * Get failed check logs
+ * Accepts either runId or PR number
  */
 export async function handleChecksLog(
 	pi: ExtensionAPI,
@@ -84,17 +85,61 @@ export async function handleChecksLog(
 	onUpdate: any,
 	ctx: ExtensionContext,
 ): Promise<any> {
-	if (!params.runId) {
+	let runId = params.runId;
+	
+	// If no runId but a PR number is provided, get the first failed run
+	if (!runId && params.number) {
+		onUpdate?.({ content: [{ type: "text", text: `Finding failed runs for PR #${params.number}...` }] });
+		
+		// Get checks for the PR
+		const checksResult = await execGh(pi, ctx,
+			["pr", "view", String(params.number), "--json", "statusCheckRollup"],
+			{ signal, timeout: 30000 },
+		);
+		
+		if (checksResult.code !== 0) {
+			return {
+				content: [{ type: "text", text: getErrorMessage(checksResult.stderr, "Get PR checks") }],
+				details: { action: "checks-log", error: checksResult.stderr, prNumber: params.number } as GhDetails,
+				isError: true,
+			};
+		}
+		
+		const checks = parseChecks(checksResult.stdout);
+		
+		// Find first failed check with a workflow run ID
+		const failedCheck = checks.find((c) => 
+			c.conclusion === "FAILURE" && c.detailsUrl && c.detailsUrl.includes("/runs/")
+		);
+		
+		if (failedCheck && failedCheck.detailsUrl) {
+			// Extract run ID from URL (e.g., https://github.com/owner/repo/actions/runs/123456)
+			const match = failedCheck.detailsUrl.match(/\/runs\/(\d+)/);
+			if (match) {
+				runId = parseInt(match[1], 10);
+			}
+		}
+		
+		if (!runId) {
+			return {
+				content: [{ type: "text", text: `No failed workflow runs found for PR #${params.number}` }],
+				details: { action: "checks-log", error: "no_failed_runs", prNumber: params.number } as GhDetails,
+				isError: true,
+			};
+		}
+	}
+	
+	if (!runId) {
 		return {
-			content: [{ type: "text", text: "Error: 'runId' parameter is required for checks-log action" }],
+			content: [{ type: "text", text: "Error: Either 'runId' or 'number' (PR number) parameter is required for checks-log action" }],
 			details: { action: "checks-log", error: "missing_run_id" } as GhDetails,
 			isError: true,
 		};
 	}
 
-	onUpdate?.({ content: [{ type: "text", text: `Fetching logs for run ${params.runId}...` }] });
+	onUpdate?.({ content: [{ type: "text", text: `Fetching logs for run ${runId}...` }] });
 
-	const result = await execGh(pi, ctx, ["run", "view", String(params.runId), "--log-failed"], {
+	const result = await execGh(pi, ctx, ["run", "view", String(runId), "--log-failed"], {
 		signal,
 		timeout: 60000,
 	});
@@ -103,13 +148,13 @@ export async function handleChecksLog(
 		// If --log-failed has no output, try regular log
 		if (result.stderr.includes("no failed jobs")) {
 			return {
-				content: [{ type: "text", text: `No failed jobs in run ${params.runId}` }],
-				details: { action: "checks-log", output: "No failed jobs", runId: params.runId } as GhDetails,
+				content: [{ type: "text", text: `No failed jobs in run ${runId}` }],
+				details: { action: "checks-log", output: "No failed jobs", runId, prNumber: params.number } as GhDetails,
 			};
 		}
 		return {
 			content: [{ type: "text", text: getErrorMessage(result.stderr, "Get run logs") }],
-			details: { action: "checks-log", error: result.stderr, runId: params.runId } as GhDetails,
+			details: { action: "checks-log", error: result.stderr, runId, prNumber: params.number } as GhDetails,
 			isError: true,
 		};
 	}
@@ -123,7 +168,7 @@ export async function handleChecksLog(
 
 	return {
 		content: [{ type: "text", text: output }],
-		details: { action: "checks-log", output: `Run ${params.runId} logs (${logs.length} chars)`, runId: params.runId } as GhDetails,
+		details: { action: "checks-log", output: `Run ${runId} logs (${logs.length} chars)`, runId, prNumber: params.number } as GhDetails,
 	};
 }
 
dots/pi/agent/extensions/github/index.ts
@@ -157,6 +157,7 @@ export default function (pi: ExtensionAPI) {
 			"Use pr-review-comments to submit a review (approve/request-changes/comment) with multiple inline comments at once. " +
 			"Use pr-reviews-list/pr-review-edit to list and edit top-level review bodies. " +
 			"Use pr-review-comments-list/pr-review-comment-edit/pr-review-comment-delete to manage inline review comments. " +
+			"Use checks-log with either runId or number (PR number) to get failed check logs - if PR number is provided, the first failed run will be used. " +
 			"Write operations (create, merge, review, comment, close, restart, edit, delete) require user approval.",
 
 		parameters: Type.Object({