Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/clawsweeper-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions tests/comment-token-failure.test.mjs
Original file line number Diff line number Diff line change
@@ -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 });
}
});
Loading