fix(ci): fail closed when comment lookup fails (#697)

Co-authored-by: ydflow <314143294+ydflow@users.noreply.github.com>
This commit is contained in:
ydflow
2026-09-22 17:21:54 +08:00
committed by GitHub
co-authored by ydflow
parent 5d78861d9f
commit 6d27a11fb9
4 changed files with 47 additions and 4 deletions
+2
View File
@@ -1659,6 +1659,8 @@ Workflow:
If the review-status API returns a non-2xx response, write mode fails closed: the job exits without writing files, committing, or pushing to the team knowledge repo.
Comment mode also fails closed when it cannot list the existing marker comment, so a transient provider error cannot create a duplicate comment.
Ready-to-use templates:
- `examples/ci/github-actions-mr-extract.yml` (GitHub Actions)
+2
View File
@@ -1607,6 +1607,8 @@ teamai ci extract-mr --url "$MR_URL" --mode write --team-repo ./team-repo --indi
如果审核状态 API 返回非 2xx 响应,write 模式会按 fail-closed 处理:任务失败退出,且不会向团队知识仓库写入文件、提交或 push。
评论模式在无法列出已有 marker 评论时也会按 fail-closed 处理,避免临时的 Provider 错误创建重复评论。
开箱即用模板:
- `examples/ci/github-actions-mr-extract.yml`(GitHub Actions)
+37 -2
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { parseMrUrl, formatComment } from '../ci/mr-comment.js';
import { afterEach, describe, it, expect, vi } from 'vitest';
import { parseMrUrl, formatComment, postOrUpdateMrComment } from '../ci/mr-comment.js';
import type { LearningDraft, CodebaseSuggestion } from '../types.js';
describe('parseMrUrl', () => {
@@ -62,3 +62,38 @@ describe('formatComment', () => {
expect(result).not.toContain('### Codebase.md 更新建议');
});
});
describe('postOrUpdateMrComment API failures', () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
});
it('fails without creating a duplicate GitHub comment when listing comments fails', async () => {
vi.stubEnv('GITHUB_TOKEN', 'test-token');
const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 503 }));
vi.stubGlobal('fetch', fetchMock);
await expect(postOrUpdateMrComment(
'https://github.com/acme/app/pull/7', undefined, undefined,
)).rejects.toThrow('GitHub comment list request failed (503)');
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][1]?.method).toBe('GET');
});
it('fails without creating a duplicate TGit note when listing notes fails', async () => {
vi.stubEnv('TGIT_TOKEN', 'test-token');
const fetchMock = vi.fn()
.mockResolvedValueOnce(new Response(JSON.stringify([{ id: 42, iid: 7 }]), { status: 200 }))
.mockResolvedValueOnce(new Response(null, { status: 503 }));
vi.stubGlobal('fetch', fetchMock);
await expect(postOrUpdateMrComment(
'https://git.woa.com/acme/app/merge_requests/7', undefined, undefined,
)).rejects.toThrow('TGit note list request failed (503)');
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(fetchMock.mock.calls.some(([, init]) => init?.method === 'POST')).toBe(false);
});
});
+6 -2
View File
@@ -133,7 +133,9 @@ async function findGitHubComment(
`/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100`,
'GET',
);
if (!resp.ok) return null;
if (!resp.ok) {
throw new Error(`GitHub comment list request failed (${resp.status})`);
}
const comments = (await resp.json()) as GitHubComment[];
return comments.find((c) => c.body.includes(marker)) ?? null;
}
@@ -224,7 +226,9 @@ async function findTGitComment(
`/projects/${projectId}/merge_requests/${mrGlobalId}/notes?per_page=100`,
'GET',
);
if (!resp.ok) return null;
if (!resp.ok) {
throw new Error(`TGit note list request failed (${resp.status})`);
}
const notes = (await resp.json()) as TGitNote[];
return notes.find((n) => n.body.includes(marker)) ?? null;
}