Skip to content

Commit da77718

Browse files
authored
add file (#1173)
Co-authored-by: Taylor Lucero <[email protected]>
1 parent 24c5ecc commit da77718

File tree

1 file changed

+215
-39
lines changed

1 file changed

+215
-39
lines changed

.github/workflows/trigger-n8n-workflow.yml

Lines changed: 215 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,19 @@ jobs:
2323
environment: n8n-sending
2424
outputs:
2525
filtered_file_count: ${{ steps.filter_files.outputs.file_count }}
26+
should_send: ${{ steps.filter_files.outputs.file_count != '0' && steps.diff_threshold.outputs.skip_send != 'true' && steps.send_payload.outputs.cancelled != 'true' && steps.send_payload.outputs.error != 'true' }}
27+
diff_char_count: ${{ steps.diff_threshold.outputs.diff_char_count }}
28+
pr_number: ${{ steps.run_context.outputs.pr_number }}
29+
repository: ${{ steps.run_context.outputs.repository }}
30+
run_url: ${{ steps.run_context.outputs.run_url }}
31+
response_payload: ${{ steps.capture_payload.outputs.payload }}
32+
cancelled: ${{ steps.send_payload.outputs.cancelled }}
33+
send_executed: ${{ steps.send_payload.outputs.executed }}
34+
send_error: ${{ steps.send_payload.outputs.error }}
2635
env:
2736
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
2837
IS_MANUAL_RUN: ${{ github.event_name == 'workflow_dispatch' }}
38+
MIN_DIFF_CHAR_THRESHOLD: ${{ vars.N8N_MIN_DIFF_CHAR_THRESHOLD || '1000' }}
2939
steps:
3040
# Step 1: Checkout repository
3141
- name: Checkout repository
@@ -36,7 +46,14 @@ jobs:
3646
id: uuid
3747
run: echo "run_token=$(uuidgen)" >> "$GITHUB_OUTPUT"
3848

39-
# Step 2b: Require dispatcher to have write/maintain/admin access
49+
- name: Capture run context
50+
id: run_context
51+
run: |
52+
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
53+
echo "repository=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
54+
echo "run_url=${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT"
55+
56+
# Step 2b: Require dispatcher to have access
4057
- name: Validate dispatcher permissions
4158
if: ${{ github.event_name == 'workflow_dispatch' }}
4259
env:
@@ -213,7 +230,16 @@ jobs:
213230
run: |
214231
gzip -c pr.diff > pr.diff.gz
215232
base64 -w 0 pr.diff.gz > diff.b64
216-
echo "::add-mask::$(cat diff.b64)"
233+
python3 - <<'PY'
234+
from pathlib import Path
235+
236+
chunk_size = 8000
237+
diff_contents = Path("diff.b64").read_text()
238+
for start in range(0, len(diff_contents), chunk_size):
239+
chunk = diff_contents[start:start + chunk_size]
240+
if chunk:
241+
print(f"::add-mask::{chunk}")
242+
PY
217243
218244
# Step 9: Inspect payload sizes for debugging
219245
- name: Debug payload size
@@ -223,38 +249,97 @@ jobs:
223249
echo "Commits metadata size: $(stat -c%s commits.json) bytes"
224250
echo "Compressed diff size: $(stat -c%s pr.diff.gz) bytes"
225251
252+
# Step 9b: Evaluate diff length against threshold
253+
- name: Evaluate diff char threshold
254+
id: diff_threshold
255+
run: |
256+
set -euo pipefail
257+
258+
THRESHOLD="${MIN_DIFF_CHAR_THRESHOLD}"
259+
if [[ ! "$THRESHOLD" =~ ^[0-9]+$ ]]; then
260+
echo "Invalid MIN_DIFF_CHAR_THRESHOLD='$THRESHOLD'; defaulting to 0"
261+
THRESHOLD=0
262+
fi
263+
264+
CHAR_COUNT=$(wc -c < pr.diff | tr -d '[:space:]')
265+
echo "diff_char_count=${CHAR_COUNT}" >> "$GITHUB_OUTPUT"
266+
echo "threshold=${THRESHOLD}" >> "$GITHUB_OUTPUT"
267+
268+
if (( CHAR_COUNT < THRESHOLD )); then
269+
echo "skip_send=true" >> "$GITHUB_OUTPUT"
270+
echo "Filtered diff char count ${CHAR_COUNT} is below threshold ${THRESHOLD}; skipping downstream send."
271+
else
272+
echo "skip_send=false" >> "$GITHUB_OUTPUT"
273+
echo "Filtered diff char count ${CHAR_COUNT} meets threshold ${THRESHOLD}; continuing."
274+
fi
275+
276+
# Step 9c: Short-circuit when below threshold
277+
- name: Diff below threshold
278+
if: ${{ steps.filter_files.outputs.file_count != '0' && steps.diff_threshold.outputs.skip_send == 'true' }}
279+
run: |
280+
echo "ℹ️ Diff char count (${{ steps.diff_threshold.outputs.diff_char_count }}) is below threshold (${{ steps.diff_threshold.outputs.threshold }}). Skipping n8n send."
281+
226282
# Step 10: Send consolidated payload to n8n
227283
- name: Combine and send to n8n webhook
228-
if: ${{ steps.filter_files.outputs.file_count != '0' }}
284+
id: send_payload
285+
if: ${{ steps.filter_files.outputs.file_count != '0' && steps.diff_threshold.outputs.skip_send != 'true' }}
229286
env:
230287
N8N_WEBHOOK_URL: ${{ secrets.N8N_WEBHOOK_URL }}
231288
N8N_SENDING_TOKEN: ${{ secrets.N8N_SENDING_TOKEN }}
289+
RUN_TOKEN: ${{ steps.uuid.outputs.run_token }}
232290
run: |
233291
set -euo pipefail
234292
235-
jq -n \
236-
--slurpfile pr pr.json \
237-
--slurpfile files files.json \
238-
--slurpfile commits commits.json \
239-
--rawfile diff_base64 diff.b64 \
240-
--arg run_token "${{ steps.uuid.outputs.run_token }}" \
241-
--arg n8n_sending_token "$N8N_SENDING_TOKEN" \
242-
'{
243-
pr: $pr[0],
244-
files: $files[0],
245-
commits: $commits[0],
246-
diff_base64: ($diff_base64 | gsub("\n"; "")),
247-
token: $run_token,
248-
n8n_sending_token: $n8n_sending_token
249-
}' > payload.json
293+
echo "cancelled=false" >> "$GITHUB_OUTPUT"
294+
echo "executed=true" >> "$GITHUB_OUTPUT"
295+
echo "error=false" >> "$GITHUB_OUTPUT"
296+
297+
python3 - <<'PY'
298+
import json
299+
import os
300+
from pathlib import Path
301+
302+
root = Path(".")
303+
304+
def read_json(filename):
305+
path = root / filename
306+
try:
307+
return json.loads(path.read_text())
308+
except FileNotFoundError:
309+
raise SystemExit(f"{filename} not found")
310+
except json.JSONDecodeError as exc:
311+
raise SystemExit(f"Unable to parse {filename}: {exc}")
312+
313+
pr = read_json("pr.json")
314+
files = read_json("files.json")
315+
commits = read_json("commits.json")
316+
317+
diff_path = root / "diff.b64"
318+
try:
319+
diff_base64 = diff_path.read_text().replace("\n", "")
320+
except FileNotFoundError:
321+
raise SystemExit("diff.b64 not found")
322+
323+
payload = {
324+
"pr": pr,
325+
"files": files,
326+
"commits": commits,
327+
"diff_base64": diff_base64,
328+
"token": os.environ.get("RUN_TOKEN", ""),
329+
"n8n_sending_token": os.environ.get("N8N_SENDING_TOKEN", "")
330+
}
331+
332+
(root / "payload.json").write_text(json.dumps(payload))
333+
PY
250334
251335
echo "::add-mask::$N8N_SENDING_TOKEN"
252336
253337
PAYLOAD_SIZE=$(stat -c%s payload.json)
254338
MAX_BYTES=$((10*1024*1024))
255339
if (( PAYLOAD_SIZE > MAX_BYTES )); then
256-
echo "Payload too large ($PAYLOAD_SIZE bytes). Aborting send."
257-
exit 1
340+
echo "Payload too large ($PAYLOAD_SIZE bytes)."
341+
echo "error=true" >> "$GITHUB_OUTPUT"
342+
exit 0
258343
fi
259344
260345
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
@@ -271,46 +356,66 @@ jobs:
271356
STATUS=$(jq -r ".status" response_body.json)
272357
MATCHED=$(jq -r ".token" response_body.json)
273358
if [ "$MATCHED" != "${{ steps.uuid.outputs.run_token }}" ] || [ "$STATUS" != "completed" ]; then
359+
if [ "$STATUS" = "cancelled" ]; then
360+
echo "n8n workflow reported cancellation; skipping downstream processing."
361+
echo "cancelled=true" >> "$GITHUB_OUTPUT"
362+
exit 0
363+
fi
274364
echo "n8n workflow failed or token mismatch"
275-
exit 1
365+
echo "error=true" >> "$GITHUB_OUTPUT"
366+
exit 0
276367
fi
277368
278369
if [ "$HTTP_STATUS" -lt 200 ] || [ "$HTTP_STATUS" -ge 300 ]; then
279370
echo "n8n workflow failed (HTTP $HTTP_STATUS)"
280-
exit 1
371+
echo "error=true" >> "$GITHUB_OUTPUT"
372+
exit 0
373+
fi
374+
375+
- name: Capture response payload
376+
id: capture_payload
377+
if: ${{ steps.filter_files.outputs.file_count != '0' && steps.diff_threshold.outputs.skip_send != 'true' && steps.send_payload.outputs.cancelled != 'true' && steps.send_payload.outputs.error != 'true' }}
378+
run: |
379+
set -euo pipefail
380+
if [ ! -s response_body.json ]; then
381+
echo "payload=" >> "$GITHUB_OUTPUT"
382+
exit 0
281383
fi
384+
base64 -w0 response_body.json > response_body.b64
385+
echo "payload=$(cat response_body.b64)" >> "$GITHUB_OUTPUT"
386+
rm -f response_body.b64
387+
388+
- name: Enforce n8n send failure
389+
if: ${{ steps.send_payload.outputs.error == 'true' }}
390+
run: |
391+
echo "n8n send step reported failure. Marking job as failed."
392+
exit 1
282393
283394
# Step 11: Short-circuit when nothing qualifies
284395
- name: No eligible files to send
285396
if: ${{ steps.filter_files.outputs.file_count == '0' }}
286397
run: echo "ℹ️ No eligible files after filtering llms/.ai paths. Skipping n8n send."
287398

288-
# Step 12: Archive n8n response for next job
289-
- name: Upload n8n response artifact
290-
if: ${{ steps.filter_files.outputs.file_count != '0' }}
291-
uses: actions/upload-artifact@v4
292-
with:
293-
name: n8n-response
294-
path: response_body.json
295-
if-no-files-found: error
296-
retention-days: 1
297-
298399
receive-validate-and-comment:
299400
runs-on: ubuntu-latest
300401
needs: gather-and-send
301-
if: ${{ needs.gather-and-send.result == 'success' && needs.gather-and-send.outputs.filtered_file_count != '0' }}
402+
if: ${{ needs.gather-and-send.result == 'success' && needs.gather-and-send.outputs.filtered_file_count != '0' && needs.gather-and-send.outputs.should_send == 'true' }}
302403
environment: n8n-receiving
303404
steps:
304405
# Step 13: Re-checkout repo (fresh workspace)
305406
- name: Checkout repository
306407
uses: actions/checkout@v4
307408

308-
# Step 14: Retrieve n8n response artifact
309-
- name: Download n8n response
310-
uses: actions/download-artifact@v4
311-
with:
312-
name: n8n-response
313-
path: .
409+
# Step 14: Restore response payload from upstream output
410+
- name: Restore response payload
411+
run: |
412+
set -euo pipefail
413+
payload='${{ needs.gather-and-send.outputs.response_payload }}'
414+
if [ -z "$payload" ]; then
415+
echo "No response payload found; exiting."
416+
exit 1
417+
fi
418+
printf '%s' "$payload" | base64 -d > response_body.json
314419
315420
# Step 15: Confirm handshake token
316421
- name: Validate receiving token
@@ -545,3 +650,74 @@ jobs:
545650
done
546651
547652
echo "✅ Done posting verification reviews."
653+
654+
# Step 18b: Clean up response artifacts
655+
- name: Remove response payload
656+
if: ${{ always() }}
657+
run: |
658+
rm -f response_body.json review_batches.json || true
659+
660+
notify-on-failure:
661+
runs-on: ubuntu-latest
662+
needs:
663+
- gather-and-send
664+
- receive-validate-and-comment
665+
if: ${{ always() && ((needs.gather-and-send.result == 'failure' && (needs.gather-and-send.outputs.send_executed != 'true' || needs.gather-and-send.outputs.send_error == 'true')) || needs.receive-validate-and-comment.result == 'failure') }}
666+
env:
667+
GATHER_RESULT: ${{ needs.gather-and-send.result }}
668+
RECEIVE_RESULT: ${{ needs.receive-validate-and-comment.result }}
669+
PR_NUMBER: ${{ needs.gather-and-send.outputs.pr_number }}
670+
REPOSITORY: ${{ needs.gather-and-send.outputs.repository || github.repository }}
671+
RUN_URL: ${{ needs.gather-and-send.outputs.run_url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
672+
WORKFLOW_NAME: ${{ github.workflow }}
673+
RUN_ID: ${{ github.run_id }}
674+
RUN_ATTEMPT: ${{ github.run_attempt }}
675+
ERROR_WEBHOOK_URL: ${{ secrets.ERROR_WEBHOOK_URL }}
676+
ACTOR: ${{ github.actor }}
677+
SEND_EXECUTED: ${{ needs.gather-and-send.outputs.send_executed }}
678+
SEND_ERROR: ${{ needs.gather-and-send.outputs.send_error }}
679+
steps:
680+
- name: Evaluate failure state
681+
id: alert
682+
run: |
683+
set -euo pipefail
684+
gather="${GATHER_RESULT:-unknown}"
685+
receive="${RECEIVE_RESULT:-skipped}"
686+
if [[ "$gather" == "failure" || "$receive" == "failure" ]]; then
687+
echo "alert=true" >> "$GITHUB_OUTPUT"
688+
echo "Failure detected (gather=$gather, receive=$receive)."
689+
else
690+
echo "alert=false" >> "$GITHUB_OUTPUT"
691+
echo "No downstream alert required (gather=$gather, receive=$receive)."
692+
fi
693+
694+
- name: Send failure webhook
695+
if: ${{ steps.alert.outputs.alert == 'true' }}
696+
run: |
697+
set -euo pipefail
698+
699+
if [[ -z "${ERROR_WEBHOOK_URL:-}" ]]; then
700+
echo "Webhook URL secret not configured; skipping alert dispatch."
701+
exit 0
702+
fi
703+
704+
gather="${GATHER_RESULT:-unknown}"
705+
receive="${RECEIVE_RESULT:-skipped}"
706+
pr="${PR_NUMBER:-unknown}"
707+
repo="${REPOSITORY:-${{ github.repository }}}"
708+
payload=$(jq -n \
709+
--arg repo "$repo" \
710+
--arg pr "$pr" \
711+
--arg gather_status "$gather" \
712+
--arg receive_status "$receive" \
713+
--arg run_url "$RUN_URL" \
714+
--arg workflow "$WORKFLOW_NAME" \
715+
--arg run_id "$RUN_ID" \
716+
--arg run_attempt "$RUN_ATTEMPT" \
717+
--arg actor "$ACTOR" \
718+
'{repository:$repo, pr_number:$pr, gather_status:$gather_status, receive_status:$receive_status, run_url:$run_url, workflow:$workflow, run_id:$run_id, run_attempt:$run_attempt, actor:$actor}' )
719+
720+
curl -sS -X POST \
721+
-H "Content-Type: application/json" \
722+
--data "$payload" \
723+
"$ERROR_WEBHOOK_URL"

0 commit comments

Comments
 (0)