Skip to content

Commit 0932f92

Browse files
feat: automated fuzzing issue creation with Claude (#5292)
# Automated Fuzzing Issue Creation When fuzzing detects crashes, Claude analyzes them and creates/updates GitHub issues automatically. ## Features - **Smart duplicate detection**: Compares crash location + error pattern using source code context - **Occurrence tracking**: Updates single comment with count (e.g., "Crash seen 15 time(s)") - **Detailed analysis**: Stack trace, root cause, debug output, direct artifact links - **Proper formatting**: Stack traces in code blocks, reproduction commands with `--sanitizer=none` ## How It Works 1. Claude reads fuzzer log and extracts crash details 2. Checks existing issues by reading source code at crash locations 3. Either creates new issue or updates occurrence counter on duplicate Uses Claude Code Action v1 with Sonnet 4.5 (~$0.03-0.05 per crash). --------- Signed-off-by: Joe Isaacs <[email protected]>
1 parent 6d747a6 commit 0932f92

File tree

1 file changed

+234
-2
lines changed

1 file changed

+234
-2
lines changed

.github/workflows/fuzz.yml

Lines changed: 234 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ jobs:
1616
- disk=large
1717
- extras=s3-cache
1818
- tag=io-fuzz
19+
outputs:
20+
crashes_found: ${{ steps.check.outputs.crashes_found }}
21+
first_crash_name: ${{ steps.check.outputs.first_crash_name }}
22+
artifact_url: ${{ steps.upload_artifacts.outputs.artifact-url }}
1923
steps:
2024
- uses: runs-on/action@v2
2125
with:
@@ -44,13 +48,47 @@ jobs:
4448
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
4549
- name: Run fuzzing target
4650
id: fuzz
47-
run: RUST_BACKTRACE=1 cargo +nightly fuzz run --release --debug-assertions file_io -- -max_total_time=7200
51+
run: |
52+
RUST_BACKTRACE=1 cargo +nightly fuzz run --release --debug-assertions file_io -- -max_total_time=7200 2>&1 | tee fuzz_output.log
4853
continue-on-error: true
54+
- name: Check for crashes
55+
id: check
56+
run: |
57+
if [ -d "fuzz/artifacts" ] && [ "$(ls -A fuzz/artifacts 2>/dev/null)" ]; then
58+
echo "crashes_found=true" >> $GITHUB_OUTPUT
59+
60+
# Get the first crash file only
61+
FIRST_CRASH=$(find fuzz/artifacts -type f \( -name "crash-*" -o -name "leak-*" -o -name "timeout-*" -o -name "oom-*" \) | head -1)
62+
63+
if [ -n "$FIRST_CRASH" ]; then
64+
echo "first_crash=$FIRST_CRASH" >> $GITHUB_OUTPUT
65+
echo "first_crash_name=$(basename $FIRST_CRASH)" >> $GITHUB_OUTPUT
66+
67+
# Count all crashes for reporting
68+
CRASH_COUNT=$(find fuzz/artifacts -type f \( -name "crash-*" -o -name "leak-*" -o -name "timeout-*" -o -name "oom-*" \) | wc -l)
69+
echo "crash_count=$CRASH_COUNT" >> $GITHUB_OUTPUT
70+
echo "Found $CRASH_COUNT crash(es), will process first: $(basename $FIRST_CRASH)"
71+
fi
72+
else
73+
echo "crashes_found=false" >> $GITHUB_OUTPUT
74+
echo "crash_count=0" >> $GITHUB_OUTPUT
75+
echo "No crashes found"
76+
fi
4977
- name: Archive crash artifacts
78+
id: upload_artifacts
79+
if: steps.check.outputs.crashes_found == 'true'
5080
uses: actions/upload-artifact@v5
5181
with:
5282
name: io-fuzzing-crash-artifacts
5383
path: fuzz/artifacts
84+
retention-days: 30
85+
- name: Archive fuzzer output log
86+
if: steps.check.outputs.crashes_found == 'true'
87+
uses: actions/upload-artifact@v4
88+
with:
89+
name: io-fuzzing-logs
90+
path: fuzz_output.log
91+
retention-days: 30
5492
- name: Persist corpus
5593
shell: bash
5694
run: |
@@ -62,9 +100,203 @@ jobs:
62100
AWS_REGION: "us-east-1"
63101
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
64102
- name: Fail job if fuzz run found a bug
65-
if: steps.fuzz.outcome == 'failure'
103+
if: steps.check.outputs.crashes_found == 'true'
66104
run: exit 1
67105

106+
report-io-fuzz-failures:
107+
name: "Report IO Fuzz Failures"
108+
needs: io_fuzz
109+
if: always() && needs.io_fuzz.outputs.crashes_found == 'true'
110+
runs-on: ubuntu-latest
111+
permissions:
112+
issues: write
113+
contents: read
114+
id-token: write
115+
pull-requests: read
116+
steps:
117+
- name: Checkout repository
118+
uses: actions/checkout@v5
119+
120+
- name: Download fuzzer logs
121+
uses: actions/download-artifact@v4
122+
with:
123+
name: io-fuzzing-logs
124+
path: ./logs
125+
126+
- name: Analyze and report crash with Claude
127+
env:
128+
CRASH_FILE: ${{ needs.io_fuzz.outputs.first_crash_name }}
129+
ARTIFACT_URL: ${{ needs.io_fuzz.outputs.artifact_url }}
130+
BRANCH: ${{ github.ref_name }}
131+
COMMIT: ${{ github.sha }}
132+
uses: anthropics/claude-code-action@v1
133+
with:
134+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
135+
github_token: ${{ secrets.GITHUB_TOKEN }}
136+
show_full_output: true
137+
prompt: |
138+
# Fuzzer Crash Analysis and Reporting
139+
140+
A fuzzing run for the `file_io` target has detected a crash. Analyze it and report by creating or updating a GitHub issue.
141+
142+
## Step 1: Analyze the Crash
143+
144+
1. Read the fuzzer log: `logs/fuzz_output.log`
145+
2. Extract:
146+
- Stack trace (lines with `#0`, `#1`, etc.)
147+
- Error message ("panicked at" or "ERROR:")
148+
- Crash location (top user code frame, not std/core/libfuzzer)
149+
- Debug output (look for "Output of `std::fmt::Debug`:" section before the crash)
150+
3. Read the source code at the crash location to understand root cause
151+
152+
## Step 2: Check for Duplicates
153+
154+
1. List existing fuzzer issues:
155+
```bash
156+
gh issue list --repo ${{ github.repository }} --label fuzzer --state open --json number,title,body --limit 50
157+
```
158+
159+
2. For each existing issue, compare:
160+
- **Crash location**: Same file + function? (line numbers can differ)
161+
- **Error pattern**: Same error after normalizing values?
162+
- "index 5 out of bounds" = "index 12 out of bounds" (SAME)
163+
- "len is 100" = "len is 5" (SAME)
164+
- Read source code if needed to verify same root cause
165+
166+
3. Determine duplication level:
167+
- **EXACT DUPLICATE**: Same crash location + same error pattern → Add reaction
168+
- **SIMILAR**: Same general area but unclear → Add comment
169+
- **NEW BUG**: Different location or different error type → Create new issue
170+
171+
## Step 3: Take Action
172+
173+
### If EXACT DUPLICATE (high confidence):
174+
Update or create a tracking comment to count occurrences:
175+
176+
1. First, check if there's already a tracking comment (look for a comment starting with "<!-- occurrences: ")
177+
2. If found, extract the count, increment it, and edit the comment
178+
3. If not found, create a new comment
179+
180+
Comment format:
181+
```markdown
182+
<!-- occurrences: N -->
183+
**Crash seen N time(s)**
184+
185+
Latest occurrence:
186+
- Crash file: $CRASH_FILE
187+
- Artifact: $ARTIFACT_URL
188+
- Branch: $BRANCH
189+
- Commit: $COMMIT
190+
```
191+
192+
Use:
193+
- `gh api repos/${{ github.repository }}/issues/ISSUE_NUM/comments` to list comments
194+
- `gh api repos/${{ github.repository }}/issues/comments/COMMENT_ID -X PATCH` to update
195+
- `gh issue comment ISSUE_NUM` to create new
196+
197+
### If SIMILAR (medium confidence):
198+
Comment on the existing issue with analysis:
199+
```bash
200+
gh issue comment ISSUE_NUM --repo ${{ github.repository }} --body "..."
201+
```
202+
203+
Include in comment:
204+
- Note that another crash was detected
205+
- Your confidence level and reasoning
206+
- Key differences if any
207+
- Crash file: $CRASH_FILE
208+
- Workflow: $WORKFLOW_RUN
209+
210+
### If NEW BUG (not a duplicate):
211+
Create a new issue with `gh issue create`:
212+
```bash
213+
gh issue create --repo ${{ github.repository }} \
214+
--title "Fuzzing Crash: [brief description]" \
215+
--label "bug,fuzzer" \
216+
--body "..."
217+
```
218+
219+
Issue body must include:
220+
```markdown
221+
## Fuzzing Crash Report
222+
223+
### Analysis
224+
225+
**Crash Location**: `file.rs:function_name`
226+
227+
**Error Message**:
228+
```
229+
[error message]
230+
```
231+
232+
**Stack Trace**:
233+
```
234+
[top 5-7 frames - keep in code block to prevent markdown rendering issues]
235+
```
236+
237+
Note: Keep stack traces in code blocks to prevent `#0`, `#1` from being interpreted as markdown headers.
238+
239+
**Root Cause**: [Your analysis]
240+
241+
<details>
242+
<summary>Debug Output</summary>
243+
244+
```
245+
[Include the complete "Output of std::fmt::Debug:" section from the fuzzer log]
246+
```
247+
</details>
248+
249+
### Summary
250+
251+
- **Target**: `file_io`
252+
- **Crash File**: `$CRASH_FILE`
253+
- **Branch**: $BRANCH
254+
- **Commit**: $COMMIT
255+
- **Crash Artifact**: $ARTIFACT_URL
256+
257+
### Reproduction
258+
259+
1. Download the crash artifact:
260+
- **Direct download**: $ARTIFACT_URL
261+
- Or find `io-fuzzing-crash-artifacts` at: $WORKFLOW_RUN
262+
- Extract the zip file
263+
264+
2. Reproduce locally:
265+
```bash
266+
# The artifact contains file_io/$CRASH_FILE
267+
cargo +nightly fuzz run --sanitizer=none file_io file_io/$CRASH_FILE
268+
```
269+
270+
3. Get full backtrace:
271+
```bash
272+
RUST_BACKTRACE=full cargo +nightly fuzz run --sanitizer=none file_io file_io/$CRASH_FILE
273+
```
274+
275+
---
276+
*Auto-created by fuzzing workflow with Claude analysis*
277+
```
278+
279+
## Important Guidelines
280+
281+
- Be conservative: when unsure, prefer creating a new issue over marking as duplicate
282+
- Focus on ROOT CAUSE, not specific values in error messages
283+
- You have full repo access - read source code to understand crashes
284+
- Only use +1 reaction for truly identical crashes
285+
286+
## Environment Variables
287+
288+
- CRASH_FILE: $CRASH_FILE
289+
- ARTIFACT_URL: $ARTIFACT_URL (direct link to crash artifact)
290+
- BRANCH: $BRANCH
291+
- COMMIT: $COMMIT
292+
293+
Start by reading `logs/fuzz_output.log`.
294+
claude_args: |
295+
--model claude-sonnet-4-5-20250929
296+
--max-turns 25
297+
--allowedTools "Read,Write,Bash(gh issue list:*),Bash(gh issue view:*),Bash(gh issue create:*),Bash(gh issue comment:*),Bash(gh api:*),Bash(echo:*),Bash(cat:*),Bash(jq:*),Bash(grep:*),Bash(cargo +nightly fuzz run:*),Bash(RUST_BACKTRACE=* cargo +nightly fuzz run:*)"
298+
299+
68300
ops_fuzz:
69301
name: "Array Operations Fuzz"
70302
timeout-minutes: 230 # almost 4 hours

0 commit comments

Comments
 (0)