From 99ee5496df2f3d10f3ffdfa54ff1921141feeb1d Mon Sep 17 00:00:00 2001 From: Willie Williams Date: Mon, 23 Feb 2026 09:46:04 -1000 Subject: [PATCH 1/2] Use random delimiters in vet-skill workflow to prevent EOF collisions Skill content containing literal "EOF" on its own line (e.g. bash heredoc examples) prematurely terminates GitHub Actions multiline output parsing. Replace all hardcoded EOF delimiters with randomized ones via openssl. --- .github/workflows/vet-skill.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/vet-skill.yml b/.github/workflows/vet-skill.yml index b667410..40cbbab 100644 --- a/.github/workflows/vet-skill.yml +++ b/.github/workflows/vet-skill.yml @@ -22,10 +22,11 @@ jobs: - name: Read skill content id: skill run: | + DELIMITER="SKILL_CONTENT_$(openssl rand -hex 16)" CONTENT=$(cat ${{ steps.changed.outputs.all_changed_files }} | head -c 10000) - echo "content<> $GITHUB_OUTPUT + echo "content<<$DELIMITER" >> $GITHUB_OUTPUT echo "$CONTENT" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "$DELIMITER" >> $GITHUB_OUTPUT # Agent 1: Claude Opus 4.6 - Anthropic's frontier model claude-opus-review: @@ -59,10 +60,11 @@ jobs: CLEAN_JSON=$(echo "$TEXT" | sed 's/^```json//g' | sed 's/^```//g' | sed 's/```$//g' | tr -d '\n' | sed 's/^[[:space:]]*//g') VERDICT=$(echo "$CLEAN_JSON" | jq -r '.verdict // "ERROR"' 2>/dev/null || echo "ERROR") + DELIMITER="CLAUDE_RESP_$(openssl rand -hex 16)" echo "verdict=$VERDICT" >> $GITHUB_OUTPUT - echo "response<> $GITHUB_OUTPUT + echo "response<<$DELIMITER" >> $GITHUB_OUTPUT echo "$CLEAN_JSON" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "$DELIMITER" >> $GITHUB_OUTPUT # Fail the job if verdict is FAIL (makes it visible in GitHub UI) if [ "$VERDICT" = "FAIL" ]; then @@ -103,20 +105,22 @@ jobs: ERROR=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -n "$ERROR" ]; then echo "API Error: $ERROR" + DELIMITER="OAI_ERR_$(openssl rand -hex 16)" echo "verdict=ERROR" >> $GITHUB_OUTPUT - echo "response<> $GITHUB_OUTPUT + echo "response<<$DELIMITER" >> $GITHUB_OUTPUT echo "{\"verdict\": \"ERROR\", \"issues\": [\"API Error: $ERROR\"], \"reasoning\": \"Failed to call OpenAI API\"}" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "$DELIMITER" >> $GITHUB_OUTPUT exit 0 fi CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "{}"') VERDICT=$(echo "$CONTENT" | jq -r '.verdict // "ERROR"' 2>/dev/null || echo "ERROR") + DELIMITER="OAI_RESP_$(openssl rand -hex 16)" echo "verdict=$VERDICT" >> $GITHUB_OUTPUT - echo "response<> $GITHUB_OUTPUT + echo "response<<$DELIMITER" >> $GITHUB_OUTPUT echo "$CONTENT" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "$DELIMITER" >> $GITHUB_OUTPUT # Fail the job if verdict is FAIL (makes it visible in GitHub UI) if [ "$VERDICT" = "FAIL" ]; then From acb0b65eb079485e154971152f3957c1d2a92b33 Mon Sep 17 00:00:00 2001 From: Willie Williams Date: Mon, 23 Feb 2026 10:55:05 -1000 Subject: [PATCH 2/2] Run vet-skill workflow on all PRs, skip gracefully when no skills changed The aggregate job is a required status check, but the workflow only triggered on paths: skills/**. PRs that don't touch skills never ran the workflow, so aggregate never reported, blocking merge forever. Remove the paths filter and gate the AI review jobs on has_skills output instead. The aggregate job always runs but short-circuits to success when no skills were changed. --- .github/workflows/vet-skill.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/vet-skill.yml b/.github/workflows/vet-skill.yml index 40cbbab..bb2bd3f 100644 --- a/.github/workflows/vet-skill.yml +++ b/.github/workflows/vet-skill.yml @@ -2,14 +2,13 @@ name: AI Security Review on: pull_request: - paths: - - 'skills/**' jobs: read-skill: runs-on: ubuntu-latest outputs: content: ${{ steps.skill.outputs.content }} + has_skills: ${{ steps.changed.outputs.any_changed }} steps: - uses: actions/checkout@v4 @@ -21,6 +20,7 @@ jobs: - name: Read skill content id: skill + if: steps.changed.outputs.any_changed == 'true' run: | DELIMITER="SKILL_CONTENT_$(openssl rand -hex 16)" CONTENT=$(cat ${{ steps.changed.outputs.all_changed_files }} | head -c 10000) @@ -31,6 +31,7 @@ jobs: # Agent 1: Claude Opus 4.6 - Anthropic's frontier model claude-opus-review: needs: read-skill + if: needs.read-skill.outputs.has_skills == 'true' runs-on: ubuntu-latest outputs: verdict: ${{ steps.analyze.outputs.verdict }} @@ -75,6 +76,7 @@ jobs: # Agent 2: GPT-5.2 - OpenAI's frontier model openai-review: needs: read-skill + if: needs.read-skill.outputs.has_skills == 'true' runs-on: ubuntu-latest outputs: verdict: ${{ steps.analyze.outputs.verdict }} @@ -130,11 +132,16 @@ jobs: # Aggregate results and post comment aggregate: - needs: [claude-opus-review, openai-review] - if: always() # Run even if AI review jobs failed (so we can post comment) + needs: [read-skill, claude-opus-review, openai-review] + if: always() # Run even if AI review jobs were skipped or failed runs-on: ubuntu-latest steps: + - name: Skip if no skills changed + if: needs.read-skill.outputs.has_skills != 'true' + run: echo "No skill files changed, skipping AI review" + - name: Aggregate Verdicts + if: needs.read-skill.outputs.has_skills == 'true' id: aggregate run: | VERDICTS="${{ needs.claude-opus-review.outputs.verdict }},${{ needs.openai-review.outputs.verdict }}" @@ -153,6 +160,7 @@ jobs: fi - name: Post Review Comment + if: needs.read-skill.outputs.has_skills == 'true' uses: actions/github-script@v7 env: CLAUDE_VERDICT: ${{ needs.claude-opus-review.outputs.verdict }} @@ -202,5 +210,5 @@ jobs: }); - name: Fail if any FAIL verdict - if: steps.aggregate.outputs.final == 'FAIL' + if: needs.read-skill.outputs.has_skills == 'true' && steps.aggregate.outputs.final == 'FAIL' run: exit 1