Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/sweep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,10 @@ jobs:

legacy-event-queue-intake:
name: Queue legacy exact-review event
if: ${{ github.event_name == 'repository_dispatch' && github.event.action != 'clawsweeper_target_sweep' && github.event.client_payload.queue_lease_id == '' && !(github.event.client_payload.target_repo == 'openclaw/clawhub' && vars.CLAWSWEEPER_ENABLE_CLAWHUB != '1') }}
# Pull-request events arrive through the direct GitHub App webhook. The
# legacy pull_request_target dispatch is a compatibility duplicate and must
# not create a second durable exact-review intent.
if: ${{ github.event_name == 'repository_dispatch' && github.event.action != 'clawsweeper_target_sweep' && github.event.client_payload.queue_lease_id == '' && (github.event.client_payload.source_event != 'pull_request_target' || github.event.client_payload.item_kind != 'pull_request') && !(github.event.client_payload.target_repo == 'openclaw/clawhub' && vars.CLAWSWEEPER_ENABLE_CLAWHUB != '1') }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down
22 changes: 22 additions & 0 deletions test/clawsweeper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2276,6 +2276,28 @@ test("sweep workflow executes only durable queue leases without runner-side admi
assert.doesNotMatch(exactReviewStep, /--codex-timeout-ms 600000/);
});

test("sweep workflow suppresses only legacy pull_request_target PR duplicate ingress", () => {
const workflow = readText(".github/workflows/sweep.yml");
const legacyIntakeBlock = workflow.slice(
workflow.indexOf("\n legacy-event-queue-intake:"),
workflow.indexOf("\n event-review-apply:"),
);
const legacyIngressWouldQueue = (sourceEvent: string, itemKind: string) =>
sourceEvent !== "pull_request_target" || itemKind !== "pull_request";

assert.equal(legacyIngressWouldQueue("pull_request_target", "pull_request"), false);
assert.equal(legacyIngressWouldQueue("pull_request", "pull_request"), true);
assert.equal(legacyIngressWouldQueue("issues", "issue"), true);
assert.equal(legacyIngressWouldQueue("issue_comment", "pull_request"), true);
assert.match(
legacyIntakeBlock,
/github\.event\.client_payload\.source_event != 'pull_request_target' \|\| github\.event\.client_payload\.item_kind != 'pull_request'/,
);
assert.match(legacyIntakeBlock, /\/internal\/exact-review\/enqueue/);
assert.doesNotMatch(legacyIntakeBlock, /source_event != 'pull_request'/);
assert.doesNotMatch(legacyIntakeBlock, /source_event != 'issue_comment'/);
});

test("sweep workflow gives high-context Codex reviews twenty minutes by default", () => {
const workflow = readText(".github/workflows/sweep.yml");

Expand Down
61 changes: 59 additions & 2 deletions test/dashboard-worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10392,6 +10392,59 @@ test("hosted webhook enqueues item events with the repository default branch", a
});
});

test("hosted webhook preserves pull-request revision and body updates", async () => {
const storage = new MemoryDurableStorage();
const queue = new ExactReviewQueue({ storage }, {});
const env = {
CLAWSWEEPER_WEBHOOK_SECRET: "test-secret",
EXACT_REVIEW_QUEUE: new MemoryDurableNamespace(queue),
};
const repository = {
full_name: "openclaw/gogcli",
default_branch: "trunk",
private: false,
archived: false,
fork: false,
has_issues: true,
};

for (const [deliveryId, action] of [
["pr-revision", "synchronize"],
["pr-body", "edited"],
] as const) {
const response = await worker.fetch(
signedGithubWebhookRequest({
event: "pull_request",
secret: "test-secret",
deliveryId,
payload: {
action,
repository,
pull_request: { number: 597 },
installation: { id: 123 },
},
}),
env,
);
assert.equal(response.status, 202);
assert.deepEqual(await response.json(), {
ok: true,
queued: true,
item_key: "openclaw/gogcli#597",
});
}

const state = (await storage.get("exact-review-queue")) as {
items: Record<
string,
{ revision: number; decision: { sourceEvent: string; sourceAction: string } }
>;
};
assert.equal(state.items["openclaw/gogcli#597"].revision, 2);
assert.equal(state.items["openclaw/gogcli#597"].decision.sourceEvent, "pull_request");
assert.equal(state.items["openclaw/gogcli#597"].decision.sourceAction, "edited");
});

test("hosted webhook requeues unlocked and close-guard removal events", async () => {
const closeGuardLabels = [
"security",
Expand Down Expand Up @@ -11103,31 +11156,35 @@ function signedGithubWebhookRequest({
event,
secret,
payload,
deliveryId,
}: {
event: string;
secret: string;
payload: unknown;
deliveryId?: string;
}) {
const body = JSON.stringify(payload);
return signedGithubWebhookBodyRequest({ event, secret, body });
return signedGithubWebhookBodyRequest({ event, secret, body, deliveryId });
}

function signedGithubWebhookBodyRequest({
event,
secret,
body,
deliveryId = "test-delivery",
}: {
event: string;
secret: string;
body: string;
deliveryId?: string;
}) {
const signature = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
return new Request("https://clawsweeper.openclaw.ai/github/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-github-event": event,
"x-github-delivery": "test-delivery",
"x-github-delivery": deliveryId,
"x-hub-signature-256": signature,
},
body,
Expand Down