Guides
In CI and AI agents
Where files are written by code, nobody opens them before a customer does. Put the check where the file is made.
Fail the build
Store the key as a repository secret named IHCF_API_KEY, then check every file the build produced. This job fails when any of them would not open cleanly.
# .github/workflows/office-files.yml
name: Office files open
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the reports
run: python build_reports.py # writes out/*.xlsx
- name: Check every file
env:
IHCF_API_KEY: ${{ secrets.IHCF_API_KEY }}
run: |
status=0
for f in out/*.xlsx out/*.docx out/*.pptx; do
[ -e "$f" ] || continue
verdict=$(curl -sf https://api.ihatecorruptfiles.xyz/v1/check \
-H "Authorization: Bearer $IHCF_API_KEY" \
-F "file=@$f" | jq -r .verdict)
echo "$f: $verdict"
case "$verdict" in ok|tolerated|unconfirmed) ;; *) status=1 ;; esac
done
exit $statusEach file is one request, so a build that writes 20 files uses 20. The free plan's 500 a month suits a pipeline that runs on pull requests, not on every commit to every branch.
Give an agent the check
An LLM writing Office files can't see Excel's repair prompt. Give it a tool that can: the findings and planned repairs are written to be read, so the agent can fix its own code and try again — or you can send the file to /v1/repair.
import os, requests
def check_office_file(path: str) -> dict:
"""Check that an .xlsx, .docx or .pptx opens cleanly in Microsoft Office.
Returns the verdict and what is wrong, so the file can be fixed and checked again."""
with open(path, "rb") as f:
r = requests.post(
"https://api.ihatecorruptfiles.xyz/v1/check",
headers={"Authorization": f"Bearer {os.environ['IHCF_API_KEY']}"},
files={"file": f},
timeout=120,
)
r.raise_for_status()
result = r.json()
return {
"verdict": result["verdict"],
"opens_in_office": result["opens_in_office"],
"problems": [f["message"] for f in result["findings"] if f["severity"] == "blocks"],
"repairs": [a["description"] for a in result["actions"] if not a["advisory"]],
}The docstring is the tool's description for the model. Keep it: it tells the agent when to call the tool and what to do with the answer.
Retrying well
429 rate_limited, 503 at_capacity and the 5xx gateway errors are worth retrying; wait for Retry-After when it is sent. quota_exceeded and daily_limit are not — their Retry-After is hours or days away. Failed requests on our side are not counted, so retrying them costs nothing.
import random, time, requests
def post_with_retry(url, attempts=4, **kwargs):
for attempt in range(attempts):
response = requests.post(url, **kwargs)
if response.status_code not in (429, 502, 503, 504):
return response
code = response.json().get("code")
if code in ("quota_exceeded", "daily_limit"):
return response # waiting minutes won't help
wait = float(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait + random.random())
return responseLimits and quotas has the numbers.