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
46 changes: 46 additions & 0 deletions .github/scripts/qwen-triage-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,52 @@ describe('qwen-triage: git exec-vector cleanup', () => {
);
});

// Steady-state regression: on a reused runner this step has already
// sanitized the config, so the next run's grep matches nothing and exits 1.
// Under the Actions default `bash -e` shell plus the script's own
// `set -o pipefail`, an unguarded grep killed the whole step exactly when
// there was nothing to clean (run 30095456731). The allowlist test below
// can't catch this: it re-assembles the pipeline without the shell flags
// and always plants non-allowlisted keys. So run the *actual* step script
// under the actual flags against the nothing-to-clean state.
describe('steady state: nothing to clean (real step script, bash -e)', () => {
// The stage-draft cleanup touches a literal /tmp glob; neuter that one
// line so the test never deletes files outside its scratch dir.
const hermetic = cleanStep.run.replace(/^rm -f \/tmp\/stage-[^\n]*$/m, ':');
let dir;

before(() => {
assert.notEqual(hermetic, cleanStep.run, 'stage-draft rm line not found');
dir = mkdtempSync(join(tmpdir(), 'triage-steady-'));
spawnSync('git', ['-C', dir, 'init', '-q']);
});

after(() => dir && rmSync(dir, { recursive: true, force: true }));

const runStep = (script) =>
spawnSync('bash', ['-e', '-c', script], {
cwd: dir,
encoding: 'utf8',
env: { ...process.env, RUNNER_TEMP: join(dir, 'rt') },
});

it('succeeds when every config key is already allowlisted', () => {
const res = runStep(hermetic);
assert.equal(res.status, 0, res.stderr || res.stdout);
assert.match(res.stdout, /stale agent state cleaned/);
});

it('negative control: without the grep guard the same state kills the step', () => {
const unguarded = hermetic.replace(' || true; }', '; }');
assert.notEqual(
unguarded,
hermetic,
'grep guard (`|| true`) not found in clean step',
);
assert.notEqual(runStep(unguarded).status, 0);
});
});

// Behavioral test: run the workflow's *actual* allowlist pattern (extracted
// from the step) against a scratch repo. Proves the regex both unsets exec
// vectors and preserves the plumbing actions/checkout needs — a broken
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/qwen-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,12 @@ jobs:
# overwrites remote.origin.url, so a planted `ext::` transport URL kept
# here is replaced before any fetch. --unset-all handles resolved
# includeIf subsections that --remove-section cannot target.
# `|| true` on the grep: no non-allowlisted keys (the steady state
# on a reused runner this step already sanitized) means grep exits
# 1, and with the default `bash -e` plus pipefail above that would
# kill the whole step exactly when there is nothing to clean.
git config --local --name-only --list 2>/dev/null \
| grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|worktree|hidedotfiles|protecthfs|protectntfs)|remote\.|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' \
| { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|worktree|hidedotfiles|protecthfs|protectntfs)|remote\.|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } \
| while IFS= read -r key; do git config --local --unset-all "$key" 2>/dev/null || true; done
HOOKS_DIR="$(git rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)"
# Match -type f OR -type l: a symlinked hook (e.g. post-checkout ->
Expand Down
Loading