Compare commits

..
4 Commits
Author SHA1 Message Date
Daniel SamsonandClaude Opus 4.6 f495ea391f Fix issue-label and pr-rework webhook handlers
Publish Image / publish (push) Successful in 22s
- issue-label: Accept action "created" from Gitea (in addition to
  "labeled" and "label_updated") — Gitea sends "created" for new labels
- pr-rework: Guard against missing review object to prevent TypeError
  crash when Gitea sends review webhook without review data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 16:12:04 +01:00
Daniel SamsonandClaude Opus 4.6 d19798dfea Add debug logging to issue-label handler
Publish Image / publish (push) Successful in 23s
Log the webhook action and labels to diagnose why label events are
being ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 15:56:19 +01:00
Daniel SamsonandClaude Opus 4.6 0812a9be37 Fix workspace deletion — use build transition API
Publish Image / publish (push) Successful in 35s
The plain DELETE endpoint returns 405 for stopped workspaces. Use
POST /workspaces/:id/builds with {"transition":"delete"} instead,
which is the correct Coder API for workspace deletion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 15:51:24 +01:00
Daniel SamsonandClaude Opus 4.6 66649022ca Fix BullMQ jobId format — colons are not allowed
Publish Image / publish (push) Successful in 26s
BullMQ throws "Custom Id cannot contain :" when jobId includes colons.
Replace colon separators with dashes in all jobId values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 15:48:47 +01:00
4 changed files with 14 additions and 6 deletions
+3 -1
View File
@@ -22,7 +22,9 @@ export function issueLabel(config: Config, queue: TaskQueue) {
return async (c: Context) => { return async (c: Context) => {
const event = await c.req.json<IssueEvent>(); const event = await c.req.json<IssueEvent>();
if (event.action !== "label_updated" && event.action !== "labeled") { console.log(`[issue-label] action="${event.action}" issue=#${event.issue.number} labels=[${event.issue.labels.map((l) => l.name).join(", ")}]`);
if (!["labeled", "label_updated", "created"].includes(event.action)) {
return c.text("ignored: not a label event", 200); return c.text("ignored: not a label event", 200);
} }
+4
View File
@@ -16,6 +16,10 @@ export function prRework(config: Config, queue: TaskQueue) {
return c.text("ignored: not a reviewed event", 200); return c.text("ignored: not a reviewed event", 200);
} }
if (!event.review) {
return c.text("ignored: no review data", 200);
}
const [org, repo] = event.repository.full_name.split("/"); const [org, repo] = event.repository.full_name.split("/");
const prNumber = event.pull_request.number; const prNumber = event.pull_request.number;
const reviewState = event.review.state.toLowerCase(); const reviewState = event.review.state.toLowerCase();
+3 -3
View File
@@ -158,7 +158,7 @@ export class TaskQueue {
"create-workspace", "create-workspace",
{ task, comment }, { task, comment },
{ {
jobId: `create:${key}`, jobId: `create-${key}`,
attempts: 3, attempts: 3,
backoff: { type: "exponential", delay: 5000 }, backoff: { type: "exponential", delay: 5000 },
}, },
@@ -198,7 +198,7 @@ export class TaskQueue {
"cleanup-workspace", "cleanup-workspace",
{ workspaceId: entry.workspaceId, workspaceName: entry.workspaceName }, { workspaceId: entry.workspaceId, workspaceName: entry.workspaceName },
{ {
jobId: `cleanup:${workspaceName}`, jobId: `cleanup-${workspaceName}`,
attempts: 5, attempts: 5,
backoff: { type: "exponential", delay: 10000 }, backoff: { type: "exponential", delay: 10000 },
}, },
@@ -308,7 +308,7 @@ export class TaskQueue {
"cleanup-workspace", "cleanup-workspace",
{ workspaceId: ws.id, workspaceName: ws.name }, { workspaceId: ws.id, workspaceName: ws.name },
{ {
jobId: `cleanup:stale-${ws.name}-${Date.now()}`, jobId: `cleanup-stale-${ws.name}-${Date.now()}`,
attempts: 5, attempts: 5,
backoff: { type: "exponential", delay: 10000 }, backoff: { type: "exponential", delay: 10000 },
}, },
+4 -2
View File
@@ -109,12 +109,14 @@ export class CoderClient {
async deleteWorkspace(workspaceId: string): Promise<void> { async deleteWorkspace(workspaceId: string): Promise<void> {
const res = await fetch( const res = await fetch(
`${this.baseUrl}/api/v2/workspaces/${workspaceId}`, `${this.baseUrl}/api/v2/workspaces/${workspaceId}/builds`,
{ {
method: "DELETE", method: "POST",
headers: { headers: {
"Content-Type": "application/json",
"Coder-Session-Token": this.token, "Coder-Session-Token": this.token,
}, },
body: JSON.stringify({ transition: "delete" }),
}, },
); );