Unverified 提交 94f00697 authored 作者: wwwillchen-bot's avatar wwwillchen-bot 提交者: GitHub

ci: cap pr-review responder retries (#2798)

## Summary - cap `pr-review-responder` auto re-request attempts at 3 retries - mark PRs with `cc:failed` and `needs-human:review-issue` when retry limit is reached - fix strict TypeScript errors in GitHub/local agent handlers so `npm run ts` passes ## Test plan - npm run fmt && npm run lint:fix && npm run ts - npm test 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2798" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: 's avatarWill Chen <willchen90@gmail.com>
上级 f6584527
......@@ -295,10 +295,16 @@ jobs:
# Use always() so retry loop continues even if Claude Code fails after pushing commits
if: steps.pr-info.outputs.should_continue == 'true' && always() && steps.retrigger.outputs.commits_pushed == 'true'
run: |
MAX_RETRIES=3
REQUEST_COUNT="${{ steps.pr-info.outputs.request_count }}"
if [ "${REQUEST_COUNT:-0}" -ge "$MAX_RETRIES" ]; then
echo "Max retries reached (${REQUEST_COUNT}/${MAX_RETRIES}), marking PR as failed for human review"
gh pr edit ${{ steps.pr-info.outputs.pr_number }} --repo ${{ github.repository }} --remove-label "cc:pending" --add-label "cc:failed" --add-label "needs-human:review-issue"
else
NEXT_COUNT=$(( ${REQUEST_COUNT:-0} + 1 ))
echo "Setting cc:request:${NEXT_COUNT} to auto-re-request review (retry ${NEXT_COUNT} of 3)"
echo "Setting cc:request:${NEXT_COUNT} to auto-re-request review (retry ${NEXT_COUNT} of ${MAX_RETRIES})"
gh pr edit ${{ steps.pr-info.outputs.pr_number }} --repo ${{ github.repository }} --remove-label "cc:pending" --add-label "cc:request:${NEXT_COUNT}"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
......
......@@ -9,3 +9,7 @@ The pre-commit hook runs `tsgo` (via `npm run ts`), which is stricter than `tsc
## ES2020 target limitations
The project's `tsconfig.app.json` targets ES2020 with `lib: ["ES2020"]`. Methods introduced in ES2021+ (like `String.prototype.replaceAll`) are not available on the `string` type. If code uses `replaceAll`, it needs an `as any` cast to avoid `TS2550: Property 'replaceAll' does not exist on type 'string'`. Do not remove these casts without updating the tsconfig target.
## `response.json()` returns `unknown`
In IPC handlers that use `node-fetch`, `await response.json()` is treated as `unknown` by `tsgo`. If you access fields directly (for example `data.message` or `data.access_token`), add an explicit cast or narrow first (for example `const data = (await response.json()) as { message?: string }`) to avoid `TS18046`.
......@@ -104,7 +104,10 @@ export async function getGithubUser(): Promise<GithubUser | null> {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!res.ok) return null;
const emails = await res.json();
const emails = (await res.json()) as Array<{
primary?: boolean;
email?: string;
}>;
const email = emails.find((e: any) => e.primary)?.email;
if (!email) return null;
......@@ -352,7 +355,11 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
}),
});
const data = await response.json();
const data = (await response.json()) as {
access_token?: string;
error?: string;
error_description?: string;
};
if (response.ok && data.access_token) {
logger.log("Successfully obtained GitHub Access Token.");
......@@ -491,13 +498,18 @@ function handleStartGithubFlow(
})
.then((res) => {
if (!res.ok) {
return res.json().then((errData) => {
return res.json().then((errData: any) => {
throw new Error(
`GitHub API Error: ${errData.error_description || res.statusText}`,
);
});
}
return res.json();
return res.json() as Promise<{
device_code: string;
interval?: number;
user_code: string;
verification_uri: string;
}>;
})
.then((data) => {
logger.info("Received device code response");
......@@ -554,13 +566,13 @@ async function handleListGithubRepos(): Promise<
);
if (!response.ok) {
const errorData = await response.json();
const errorData = (await response.json()) as any;
throw new Error(
`GitHub API error: ${errorData.message || response.statusText}`,
);
}
const repos = await response.json();
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
full_name: repo.full_name,
......@@ -597,13 +609,13 @@ async function handleGetRepoBranches(
);
if (!response.ok) {
const errorData = await response.json();
const errorData = (await response.json()) as any;
throw new Error(
`GitHub API error: ${errorData.message || response.statusText}`,
);
}
const branches = await response.json();
const branches = (await response.json()) as any[];
return branches.map((branch: any) => ({
name: branch.name,
commit: { sha: branch.commit.sha },
......@@ -635,8 +647,8 @@ async function handleIsRepoAvailable(
(await fetch(`${GITHUB_API_BASE}/user`, {
headers: { Authorization: `Bearer ${accessToken}` },
})
.then((r) => r.json())
.then((u) => u.login));
.then((r) => r.json() as Promise<{ login?: string }>)
.then((u) => u.login ?? ""));
// Check if repo exists (using normalized name)
const url = `${GITHUB_API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(normalizedRepo)}`;
const res = await fetch(url, {
......@@ -647,7 +659,7 @@ async function handleIsRepoAvailable(
} else if (res.ok) {
return { available: false, error: "Repository already exists." };
} else {
const data = await res.json();
const data = (await res.json()) as any;
return { available: false, error: data.message || "Unknown error" };
}
} catch (err: any) {
......@@ -681,8 +693,8 @@ async function handleCreateRepo(
const userRes = await fetch(`${GITHUB_API_BASE}/user`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const user = await userRes.json();
owner = user.login;
const user = (await userRes.json()) as { login?: string };
owner = user.login ?? "";
}
// Create repo
const createUrl = org
......@@ -703,7 +715,7 @@ async function handleCreateRepo(
if (!res.ok) {
let errorMessage = `Failed to create repository (${res.status} ${res.statusText})`;
try {
const data = await res.json();
const data = (await res.json()) as any;
logger.error("GitHub API error when creating repo:", {
status: res.status,
statusText: res.statusText,
......@@ -793,7 +805,7 @@ async function handleConnectToExistingRepo(
);
if (!repoResponse.ok) {
const errorData = await repoResponse.json();
const errorData = (await repoResponse.json()) as { message?: string };
throw new Error(
`Repository not found or access denied: ${errorData.message}`,
);
......@@ -1047,7 +1059,7 @@ async function handleListCollaborators(
);
}
const collaborators = await response.json();
const collaborators = (await response.json()) as any[];
return collaborators.map((c: any) => ({
login: c.login,
avatar_url: c.avatar_url,
......@@ -1115,7 +1127,7 @@ async function handleInviteCollaborator(
);
if (!response.ok) {
const data = await response.json();
const data = (await response.json()) as { message?: string };
throw new Error(
data.message ||
`Failed to invite collaborator: ${response.status} ${response.statusText}`,
......@@ -1155,7 +1167,7 @@ async function handleRemoveCollaborator(
);
if (!response.ok) {
const data = await response.json();
const data = (await response.json()) as { message?: string };
throw new Error(
data.message ||
`Failed to remove collaborator: ${response.status} ${response.statusText}`,
......
......@@ -429,7 +429,7 @@ export async function handleLocalAgentStream(
// Ensure .dyad/ is gitignored (idempotent; also done by compaction/plans)
// Skip in read-only/plan-only mode to avoid modifying the workspace
if (!readOnly && !planModeOnly) {
await ensureDyadGitignored(appPath).catch((err) =>
await ensureDyadGitignored(appPath).catch((err: unknown) =>
logger.warn("Failed to ensure .dyad gitignored:", err),
);
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论