Running Food-Cost Diffs as GitHub Actions Gates
This page walks a food-tech developer through wiring the food-cost diff into a GitHub Actions workflow so that a pull request touching recipe or mapping data cannot merge until the margin impact has been computed and reviewed. It is the platform-specific implementation of food-cost diff checks in CI/CD; read that for the basket, baseline, and tolerance design, then follow the steps here to make the check a required status on your default branch.
The goal is a gate that runs in seconds, comments the per-item deltas directly on the pull request, and returns a non-zero exit code when a regression exceeds tolerance — which, once configured as a required check, blocks the merge button.
Prerequisites and Data Contract
- A GitHub repository holding recipe data (
recipes/,baselines/basket_costs.csv) and the costing package. - Python 3.11+ with the costing dependencies installable via
pip install -e .. - A
scripts/food_cost_diff.pyentry point that computes failures and exits0/1, reusing the shared diff logic. - Permission to configure branch protection and a
GITHUB_TOKENwithpull-requests: writefor commenting.
The workflow’s contract: trigger only on changes to cost-relevant paths, produce a machine-readable diff artifact, and surface a pass/fail status the branch protection rule can require.
Step-by-Step Implementation
Step 1 — Trigger only on cost-relevant paths
Scope the workflow to recipe and mapping data so unrelated pull requests do not pay for the check.
# .github/workflows/food-cost-diff.yml
name: food-cost-diff
on:
pull_request:
paths:
- "recipes/**"
- "mappings/**"
- "baselines/**"
permissions:
contents: read
pull-requests: write
Step 2 — Run the diff and capture its output
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -e .
- name: Run food-cost diff
id: diff
run: |
python scripts/food_cost_diff.py --format markdown > diff.md || echo "failed=true" >> "$GITHUB_OUTPUT"
- name: Upload diff artifact
uses: actions/upload-artifact@v4
with:
name: food-cost-diff
path: diff.md
Step 3 — Comment the diff on the pull request
Post the per-item deltas so a reviewer sees the margin impact inline.
- name: Comment diff
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('diff.md', 'utf8');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: "### Food-cost diff\n\n" + body,
});
Step 4 — Fail the job so the status blocks merge
Re-assert the exit code after commenting, so the comment always posts but a regression still fails the check.
- name: Enforce verdict
if: steps.diff.outputs.failed == 'true'
run: |
echo "::error::food-cost regression exceeds tolerance"
exit 1
Step 5 — Make the check required
In branch protection for the default branch, add food-cost-diff / diff to the required status checks. Now a failing diff greys out the merge button until the baseline is updated or the recipe change is corrected.
Verification and Validation
- Path filter works. Open a PR editing only a README; the workflow should not run. Edit a file under
recipes/; it should. - Comment posts on both outcomes. A passing PR shows a “all within tolerance” comment; a regressing PR shows the delta table. The comment must appear even when the job ultimately fails.
- Required check blocks. With branch protection on, confirm a regressing PR shows a red required check and a disabled merge button; approving reviewers cannot override without an audited admin bypass.
- Determinism. Re-run the workflow on the same commit; the diff artifact must be identical, confirming the check is a pure function of committed data.
Gotchas and Edge Cases
github-scriptneeds the comment step before the failure. If youexit 1in the same step that computes the diff, the comment step is skipped and reviewers see a red X with no explanation. Separate compute, comment, then enforce.- Forked-PR token scope. Pull requests from forks receive a read-only
GITHUB_TOKEN, so the comment step fails. Usepull_request_targetcautiously, or post the diff as a check-run summary instead of a comment. - Path filter false-negatives. A change to the costing code (not data) can move margin but not match the
pathsfilter. Include the costing package path, or run the gate on all PRs and keep it fast. - Baseline updates in the same PR. When a margin change is intentional, the contributor updates
baselines/basket_costs.csvin the same PR; the diff then shows a sanctioned change and passes. Do not widen tolerance to force a merge.
Related
- Food-Cost Diff Checks in CI/CD — the basket, baseline, and tolerance design this workflow runs.
- Failing a Build on Margin-Regression Thresholds — turning deltas into a hard, tunable failure.
- Pre-Commit Hooks for Recipe Cost Validation — catching the same regressions before CI.
- Reporting, CI/CD & Delivery Pipelines — the wider delivery domain.
For platform specifics, see the official GitHub Actions documentation.