Add PR progress comment and increase Cursor review timeout.
Post a temporary in-progress comment while reviewing, remove it when done, default timeout to 10 minutes, and skip retries on timeout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -7,6 +7,6 @@ PORT=8787
|
||||
|
||||
DEFAULT_BASE_BRANCH=main
|
||||
MAX_INLINE_COMMENTS=5
|
||||
REVIEW_TIMEOUT_MS=120000
|
||||
REVIEW_TIMEOUT_MS=600000
|
||||
DEDUPE_TTL_SECONDS=1800
|
||||
LOG_LEVEL=info
|
||||
|
||||
@@ -154,7 +154,7 @@ Optional:
|
||||
|
||||
- `DEFAULT_BASE_BRANCH` (default: `main`)
|
||||
- `MAX_INLINE_COMMENTS` (default: `5`)
|
||||
- `REVIEW_TIMEOUT_MS` (default: `120000`)
|
||||
- `REVIEW_TIMEOUT_MS` (default: `600000`, 10 minutes)
|
||||
- `DEDUPE_TTL_SECONDS` (default: `1800`)
|
||||
- `LOG_LEVEL` (default: `info`; options: `debug`, `info`, `warn`, `error`)
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ const envSchema = z.object({
|
||||
PORT: z.coerce.number().int().positive().default(8787),
|
||||
DEFAULT_BASE_BRANCH: z.string().default("main"),
|
||||
MAX_INLINE_COMMENTS: z.coerce.number().int().positive().default(5),
|
||||
REVIEW_TIMEOUT_MS: z.coerce.number().int().positive().default(120000),
|
||||
REVIEW_TIMEOUT_MS: z.coerce.number().int().positive().default(600000),
|
||||
DEDUPE_TTL_SECONDS: z.coerce.number().int().positive().default(1800)
|
||||
});
|
||||
|
||||
|
||||
@@ -59,6 +59,24 @@ export class GiteaClient {
|
||||
});
|
||||
}
|
||||
|
||||
async createIssueComment(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueIndex: number,
|
||||
body: string
|
||||
): Promise<{ id: number }> {
|
||||
return this.requestJson(`/repos/${owner}/${repo}/issues/${issueIndex}/comments`, {
|
||||
method: "POST",
|
||||
body: { body }
|
||||
});
|
||||
}
|
||||
|
||||
async deleteIssueComment(owner: string, repo: string, commentId: number): Promise<void> {
|
||||
await this.requestJson(`/repos/${owner}/${repo}/issues/comments/${commentId}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
}
|
||||
|
||||
async getFileIfExists(owner: string, repo: string, path: string, ref: string): Promise<string | null> {
|
||||
const encodedPath = path
|
||||
.split("/")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { GiteaClient } from "./client.js";
|
||||
|
||||
export const PROGRESS_COMMENT_MARKER = "<!-- gitea-pr-review-bot-progress -->";
|
||||
|
||||
export const PROGRESS_COMMENT_BODY =
|
||||
`${PROGRESS_COMMENT_MARKER}\n\n` +
|
||||
"🤖 **PR review in progress…** This comment will be removed when the review is ready.";
|
||||
|
||||
export async function postProgressComment(input: {
|
||||
gitea: GiteaClient;
|
||||
owner: string;
|
||||
repo: string;
|
||||
prNumber: number;
|
||||
}): Promise<number> {
|
||||
const comment = await input.gitea.createIssueComment(
|
||||
input.owner,
|
||||
input.repo,
|
||||
input.prNumber,
|
||||
PROGRESS_COMMENT_BODY
|
||||
);
|
||||
return comment.id;
|
||||
}
|
||||
|
||||
export async function deleteProgressComment(input: {
|
||||
gitea: GiteaClient;
|
||||
owner: string;
|
||||
repo: string;
|
||||
commentId: number;
|
||||
}): Promise<void> {
|
||||
await input.gitea.deleteIssueComment(input.owner, input.repo, input.commentId);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { runCursorReview } from "../cursor/review-agent.js";
|
||||
import { DedupeStore } from "../domain/dedupe-store.js";
|
||||
import { shouldProcessEvent } from "../domain/should-process-event.js";
|
||||
import { GiteaClient } from "../gitea/client.js";
|
||||
import { deleteProgressComment, postProgressComment } from "../gitea/comments-api.js";
|
||||
import { deletePriorBotReviews, postReview } from "../gitea/review-api.js";
|
||||
import { removeBotFromReviewers } from "../gitea/reviewer-api.js";
|
||||
import { buildReviewPrompt } from "../prompt/build-review-prompt.js";
|
||||
@@ -113,6 +114,23 @@ async function executeReview(params: {
|
||||
return "skipped";
|
||||
}
|
||||
|
||||
let progressCommentId: number | undefined;
|
||||
try {
|
||||
try {
|
||||
progressCommentId = await postProgressComment({ gitea, owner, repo, prNumber });
|
||||
log("info", "Posted progress comment on PR", {
|
||||
correlation_id: input.correlationId,
|
||||
pr_number: prNumber,
|
||||
comment_id: progressCommentId
|
||||
});
|
||||
} catch (error) {
|
||||
log("warn", "Failed to post progress comment on PR", {
|
||||
correlation_id: input.correlationId,
|
||||
pr_number: prNumber,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
|
||||
const files = await retry({
|
||||
fn: () => gitea.getPullFiles(owner, repo, prNumber),
|
||||
retries: 2,
|
||||
@@ -138,10 +156,11 @@ async function executeReview(params: {
|
||||
model: repoConfig.model,
|
||||
correlationId: input.correlationId
|
||||
}),
|
||||
retries: 2,
|
||||
retries: 1,
|
||||
initialDelayMs: 500,
|
||||
operationName: "runCursorReview",
|
||||
correlationId: input.correlationId
|
||||
correlationId: input.correlationId,
|
||||
shouldRetry: (error) => !isTimeoutError(error)
|
||||
});
|
||||
|
||||
await retry({
|
||||
@@ -202,4 +221,27 @@ async function executeReview(params: {
|
||||
outcome: "success"
|
||||
});
|
||||
return "success";
|
||||
} finally {
|
||||
if (progressCommentId !== undefined) {
|
||||
try {
|
||||
await deleteProgressComment({ gitea, owner, repo, commentId: progressCommentId });
|
||||
log("info", "Removed progress comment from PR", {
|
||||
correlation_id: input.correlationId,
|
||||
pr_number: prNumber,
|
||||
comment_id: progressCommentId
|
||||
});
|
||||
} catch (error) {
|
||||
log("warn", "Failed to remove progress comment from PR", {
|
||||
correlation_id: input.correlationId,
|
||||
pr_number: prNumber,
|
||||
comment_id: progressCommentId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isTimeoutError(error: unknown): boolean {
|
||||
return error instanceof Error && error.message.toLowerCase().includes("timed out");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user