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: ...@@ -295,10 +295,16 @@ jobs:
# Use always() so retry loop continues even if Claude Code fails after pushing commits # 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' if: steps.pr-info.outputs.should_continue == 'true' && always() && steps.retrigger.outputs.commits_pushed == 'true'
run: | run: |
MAX_RETRIES=3
REQUEST_COUNT="${{ steps.pr-info.outputs.request_count }}" 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 )) 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}" gh pr edit ${{ steps.pr-info.outputs.pr_number }} --repo ${{ github.repository }} --remove-label "cc:pending" --add-label "cc:request:${NEXT_COUNT}"
fi
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
......
...@@ -9,3 +9,7 @@ The pre-commit hook runs `tsgo` (via `npm run ts`), which is stricter than `tsc ...@@ -9,3 +9,7 @@ The pre-commit hook runs `tsgo` (via `npm run ts`), which is stricter than `tsc
## ES2020 target limitations ## 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. 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> { ...@@ -104,7 +104,10 @@ export async function getGithubUser(): Promise<GithubUser | null> {
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
}); });
if (!res.ok) return null; 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; const email = emails.find((e: any) => e.primary)?.email;
if (!email) return null; if (!email) return null;
...@@ -352,7 +355,11 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) { ...@@ -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) { if (response.ok && data.access_token) {
logger.log("Successfully obtained GitHub Access Token."); logger.log("Successfully obtained GitHub Access Token.");
...@@ -491,13 +498,18 @@ function handleStartGithubFlow( ...@@ -491,13 +498,18 @@ function handleStartGithubFlow(
}) })
.then((res) => { .then((res) => {
if (!res.ok) { if (!res.ok) {
return res.json().then((errData) => { return res.json().then((errData: any) => {
throw new Error( throw new Error(
`GitHub API Error: ${errData.error_description || res.statusText}`, `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) => { .then((data) => {
logger.info("Received device code response"); logger.info("Received device code response");
...@@ -554,13 +566,13 @@ async function handleListGithubRepos(): Promise< ...@@ -554,13 +566,13 @@ async function handleListGithubRepos(): Promise<
); );
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = (await response.json()) as any;
throw new Error( throw new Error(
`GitHub API error: ${errorData.message || response.statusText}`, `GitHub API error: ${errorData.message || response.statusText}`,
); );
} }
const repos = await response.json(); const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({ return repos.map((repo: any) => ({
name: repo.name, name: repo.name,
full_name: repo.full_name, full_name: repo.full_name,
...@@ -597,13 +609,13 @@ async function handleGetRepoBranches( ...@@ -597,13 +609,13 @@ async function handleGetRepoBranches(
); );
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = (await response.json()) as any;
throw new Error( throw new Error(
`GitHub API error: ${errorData.message || response.statusText}`, `GitHub API error: ${errorData.message || response.statusText}`,
); );
} }
const branches = await response.json(); const branches = (await response.json()) as any[];
return branches.map((branch: any) => ({ return branches.map((branch: any) => ({
name: branch.name, name: branch.name,
commit: { sha: branch.commit.sha }, commit: { sha: branch.commit.sha },
...@@ -635,8 +647,8 @@ async function handleIsRepoAvailable( ...@@ -635,8 +647,8 @@ async function handleIsRepoAvailable(
(await fetch(`${GITHUB_API_BASE}/user`, { (await fetch(`${GITHUB_API_BASE}/user`, {
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
}) })
.then((r) => r.json()) .then((r) => r.json() as Promise<{ login?: string }>)
.then((u) => u.login)); .then((u) => u.login ?? ""));
// Check if repo exists (using normalized name) // Check if repo exists (using normalized name)
const url = `${GITHUB_API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(normalizedRepo)}`; const url = `${GITHUB_API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(normalizedRepo)}`;
const res = await fetch(url, { const res = await fetch(url, {
...@@ -647,7 +659,7 @@ async function handleIsRepoAvailable( ...@@ -647,7 +659,7 @@ async function handleIsRepoAvailable(
} else if (res.ok) { } else if (res.ok) {
return { available: false, error: "Repository already exists." }; return { available: false, error: "Repository already exists." };
} else { } else {
const data = await res.json(); const data = (await res.json()) as any;
return { available: false, error: data.message || "Unknown error" }; return { available: false, error: data.message || "Unknown error" };
} }
} catch (err: any) { } catch (err: any) {
...@@ -681,8 +693,8 @@ async function handleCreateRepo( ...@@ -681,8 +693,8 @@ async function handleCreateRepo(
const userRes = await fetch(`${GITHUB_API_BASE}/user`, { const userRes = await fetch(`${GITHUB_API_BASE}/user`, {
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
}); });
const user = await userRes.json(); const user = (await userRes.json()) as { login?: string };
owner = user.login; owner = user.login ?? "";
} }
// Create repo // Create repo
const createUrl = org const createUrl = org
...@@ -703,7 +715,7 @@ async function handleCreateRepo( ...@@ -703,7 +715,7 @@ async function handleCreateRepo(
if (!res.ok) { if (!res.ok) {
let errorMessage = `Failed to create repository (${res.status} ${res.statusText})`; let errorMessage = `Failed to create repository (${res.status} ${res.statusText})`;
try { try {
const data = await res.json(); const data = (await res.json()) as any;
logger.error("GitHub API error when creating repo:", { logger.error("GitHub API error when creating repo:", {
status: res.status, status: res.status,
statusText: res.statusText, statusText: res.statusText,
...@@ -793,7 +805,7 @@ async function handleConnectToExistingRepo( ...@@ -793,7 +805,7 @@ async function handleConnectToExistingRepo(
); );
if (!repoResponse.ok) { if (!repoResponse.ok) {
const errorData = await repoResponse.json(); const errorData = (await repoResponse.json()) as { message?: string };
throw new Error( throw new Error(
`Repository not found or access denied: ${errorData.message}`, `Repository not found or access denied: ${errorData.message}`,
); );
...@@ -1047,7 +1059,7 @@ async function handleListCollaborators( ...@@ -1047,7 +1059,7 @@ async function handleListCollaborators(
); );
} }
const collaborators = await response.json(); const collaborators = (await response.json()) as any[];
return collaborators.map((c: any) => ({ return collaborators.map((c: any) => ({
login: c.login, login: c.login,
avatar_url: c.avatar_url, avatar_url: c.avatar_url,
...@@ -1115,7 +1127,7 @@ async function handleInviteCollaborator( ...@@ -1115,7 +1127,7 @@ async function handleInviteCollaborator(
); );
if (!response.ok) { if (!response.ok) {
const data = await response.json(); const data = (await response.json()) as { message?: string };
throw new Error( throw new Error(
data.message || data.message ||
`Failed to invite collaborator: ${response.status} ${response.statusText}`, `Failed to invite collaborator: ${response.status} ${response.statusText}`,
...@@ -1155,7 +1167,7 @@ async function handleRemoveCollaborator( ...@@ -1155,7 +1167,7 @@ async function handleRemoveCollaborator(
); );
if (!response.ok) { if (!response.ok) {
const data = await response.json(); const data = (await response.json()) as { message?: string };
throw new Error( throw new Error(
data.message || data.message ||
`Failed to remove collaborator: ${response.status} ${response.statusText}`, `Failed to remove collaborator: ${response.status} ${response.statusText}`,
......
...@@ -429,7 +429,7 @@ export async function handleLocalAgentStream( ...@@ -429,7 +429,7 @@ export async function handleLocalAgentStream(
// Ensure .dyad/ is gitignored (idempotent; also done by compaction/plans) // Ensure .dyad/ is gitignored (idempotent; also done by compaction/plans)
// Skip in read-only/plan-only mode to avoid modifying the workspace // Skip in read-only/plan-only mode to avoid modifying the workspace
if (!readOnly && !planModeOnly) { if (!readOnly && !planModeOnly) {
await ensureDyadGitignored(appPath).catch((err) => await ensureDyadGitignored(appPath).catch((err: unknown) =>
logger.warn("Failed to ensure .dyad gitignored:", err), logger.warn("Failed to ensure .dyad gitignored:", err),
); );
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论