From 4f2cb48c9e8c1d335aaae5b63207b374300cd9c4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 16 Oct 2026 12:25:01 -0700 Subject: [PATCH] fix: dispatch commands after acknowledgement token failure Co-authored-by: Sebastien Tardif --- .github/workflows/clawsweeper-dispatch.yml | 1 + CHANGELOG.md | 1 + README.md | 3 + tests/comment-token-failure.test.mjs | 75 ++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 tests/comment-token-failure.test.mjs diff --git a/.github/workflows/clawsweeper-dispatch.yml b/.github/workflows/clawsweeper-dispatch.yml index 7d23d2d..e1fc863 100644 --- a/.github/workflows/clawsweeper-dispatch.yml +++ b/.github/workflows/clawsweeper-dispatch.yml @@ -64,6 +64,7 @@ jobs: steps.comment_filter.outputs.is_command == 'true' && env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }} + continue-on-error: true uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 with: client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b791928..a6912ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Ignore maintainer opt-in reminders in the comment prefilter so quoted commands do not queue another dispatch. Thanks @SebTardif (#4). +- Keep comment commands dispatching when the optional target acknowledgement token cannot be minted. Thanks @SebTardif (#5). ## 1.0.1 — 2026-10-16 diff --git a/README.md b/README.md index 8512273..3248c71 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ Ignored bot label events also use a separate group. Marked maintainer opt-in reminders are ignored even when they quote a command; ordinary comment commands continue to use the shared router's existing command set. +Comment dispatch uses its own credential and continues if the optional token for +reactions and queued acknowledgements cannot be created. + ## Deploy This repo is wired to a Vercel project under the `amantus` team. Production diff --git a/tests/comment-token-failure.test.mjs b/tests/comment-token-failure.test.mjs new file mode 100644 index 0000000..339061b --- /dev/null +++ b/tests/comment-token-failure.test.mjs @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; + +const workflow = readFileSync(new URL('../.github/workflows/clawsweeper-dispatch.yml', import.meta.url), 'utf8'); + +function steps(source) { + const marks = []; + const pattern = /^ {6}- name: (.+)$/gm; + for (const match of source.matchAll(pattern)) { + marks.push({ name: match[1], index: match.index }); + } + return marks.map((mark, index) => { + const block = source.slice(mark.index, marks[index + 1]?.index ?? source.length); + return { + name: mark.name, + block, + continueOnError: /^ {8}continue-on-error:\s*true\s*$/m.test(block), + }; + }); +} + +function namesRunAfterFailure(source, failedName) { + const parsed = steps(source); + const failedAt = parsed.findIndex((step) => step.name === failedName); + assert.ok(failedAt >= 0, failedName); + if (parsed[failedAt].continueOnError) return parsed.map((step) => step.name); + return parsed.slice(0, failedAt + 1).map((step) => step.name); +} + +function runScript(source) { + const parsed = steps(source); + const step = parsed.find((item) => item.name === 'Acknowledge and dispatch ClawSweeper comment'); + const match = step.block.match(/\n {8}run: \|\n([\s\S]*)$/); + assert.ok(match, 'comment dispatch script must exist'); + return match[1].replace(/^ {10}/gm, '').replace(/\n*$/, ''); +} + +test('a failed target token still dispatches the comment command', () => { + const ran = namesRunAfterFailure(workflow, 'Create target comment token'); + assert.ok( + ran.includes('Acknowledge and dispatch ClawSweeper comment'), + `skipped steps after token failure: ${ran.join(', ')}`, + ); + + const root = mkdtempSync(join(tmpdir(), 'comment-token-')); + try { + const log = join(root, 'gh.log'); + const gh = join(root, 'gh'); + writeFileSync(gh, `#!/bin/sh\nprintf '%s\\n' "$*" >> ${JSON.stringify(log)}\nexit 0\n`); + chmodSync(gh, 0o755); + const result = spawnSync('bash', ['-e', '-c', runScript(workflow)], { + encoding: 'utf8', + env: { + PATH: `${root}:${process.env.PATH}`, + DISPATCH_TOKEN: 'dispatch-token', + TARGET_TOKEN: '', + TARGET_REPO: 'example/project', + ITEM_NUMBER: '42', + COMMENT_ID: '100', + COMMENT_BODY: 'please /review', + AUTHOR_ASSOCIATION: 'NONE', + SOURCE_ACTION: 'created', + RUNNER_TEMP: root, + }, + }); + assert.equal(result.status, 0, result.stderr); + assert.match(readFileSync(log, 'utf8'), /repos\/openclaw\/clawsweeper\/dispatches/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +});