See what a pull request does to your agent's cost
Add one GitHub Actions workflow and every pull request that changes LangGraph, CrewAI or OpenAI Agents SDK code gets a comment with the estimated cost per run before and after, file by file and step by step. Catch the switch to a bigger model before it reaches your bill.
Part of the Monthly plan. Free for signed-in accounts while paid plans are in beta.
What the comment looks like
Here a pull request moves the Writer agent in the CrewAI example from gpt-4o-mini to gpt-4o.
Agent cost check
Estimated cost per run for 1 agent file touched by this pull request.
| File | Framework | Before | After | Change |
|---|---|---|---|---|
| crew/brief.py | CrewAI | $0.0933 | $0.1199 | +29% |
Steps that changed
| Step | Model | Before | After |
|---|---|---|---|
| Draft | gpt-4o-mini → gpt-4o | $0.0125 | $0.0391 |
Set it up
1. Create a token. Name it after the repository so you know which one to delete later.
Checking your account…
2. Add it to the repository. In GitHub, open Settings, then Secrets and variables, then Actions, and add a repository secret named NODAGENTRY_TOKEN with the token as its value.
3. Add the workflow. Save this as .github/workflows/agent-cost.yml on your default branch. The next pull request that touches agent code gets a comment, and later pushes update the same comment.
# .github/workflows/agent-cost.yml
name: Agent cost check
on:
pull_request:
paths: ["**/*.py", "**/*.js", "**/*.ts", "**/*.mjs", "**/*.cjs", "**/*.mts", "**/*.cts"]
permissions:
contents: read
pull-requests: write
jobs:
cost:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/github-script@v7
env:
NODAGENTRY_TOKEN: ${{ secrets.NODAGENTRY_TOKEN }}
# Optional: fail the check when a file's estimated cost rises by more than this %.
FAIL_ABOVE_PCT: ""
# Optional: a typical task for your agent, used for the estimate.
NODAGENTRY_TASK: ""
with:
script: |
const { execFileSync } = require("child_process");
const token = process.env.NODAGENTRY_TOKEN;
if (!token) {
core.warning("Add a NODAGENTRY_TOKEN repository secret to run the agent cost check.");
return;
}
const pr = context.payload.pull_request;
const git = (...args) => execFileSync("git", args, { encoding: "utf8", maxBuffer: 64 << 20 });
const base = git("merge-base", pr.base.sha, pr.head.sha).trim();
const changed = git("diff", "--name-only", "-z", base, pr.head.sha)
.split("\0")
.filter((p) => /\.(py|[cm]?[jt]s)$/.test(p));
const AGENT = /crewai|@openai\/agents|from\s+agents|langgraph|StateGraph|add_?[nN]ode\s*\(|create_?[rR]eact_?[aA]gent|\bCrew\s*\(|Runner\.run/;
const read = (sha, path) => {
try {
return execFileSync("git", ["show", sha + ":" + path], { encoding: "utf8", maxBuffer: 64 << 20, stdio: ["ignore", "pipe", "ignore"] });
} catch {
return null;
}
};
const pick = (sha) => changed
.map((path) => ({ path, content: read(sha, path) }))
.filter((f) => f.content !== null && f.content.length <= 200000 && AGENT.test(f.content))
.slice(0, 100);
const files = { base: pick(base), head: pick(pr.head.sha) };
if (!files.base.length && !files.head.length) {
core.info("No agent code changed in this pull request.");
return;
}
const url = (process.env.NODAGENTRY_URL || "https://nodagentry.com") + "/api/cost-check";
const res = await fetch(url, {
method: "POST",
headers: { authorization: "Bearer " + token, "content-type": "application/json" },
body: JSON.stringify({ ...files, task: process.env.NODAGENTRY_TASK || undefined }),
});
const out = await res.json().catch(() => ({}));
if (!res.ok) {
core.setFailed("Nodagentry: " + (out.error || "HTTP " + res.status));
return;
}
const marker = "<!-- nodagentry-cost-check -->";
const body = marker + "\n" + out.markdown;
const { owner, repo } = context.repo;
const issue_number = pr.number;
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 });
const mine = comments.find((c) => c.body && c.body.startsWith(marker));
if (mine) await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body });
else await github.rest.issues.createComment({ owner, repo, issue_number, body });
const limit = Number(process.env.FAIL_ABOVE_PCT);
if (limit > 0 && out.maxIncreasePct > limit) {
core.setFailed("Estimated cost per run rose " + out.maxIncreasePct + "% (limit " + limit + "%).");
}
Options
FAIL_ABOVE_PCT: fail the check when a file's estimated cost per run rises by more than this percentage. Empty means comment only.NODAGENTRY_TASK: a typical request for your agent. Longer tasks mean more input tokens in the estimate. Leave it empty for a short support request.
How it estimates
The check uses the same importers as the editor. Each step's model is mapped to a price tier, and its tokens are estimated from the task and the kind of step. Every step is counted once, so loops and retries aren't multiplied, and tools add no model cost. Steps are matched between the two versions by name, so a new step doesn't shift the estimate of the others. The numbers are for comparing versions, not a provider bill.
Limits
- Up to 100 changed agent files per pull request, each up to 200,000 characters.
- 500 checks per token per day, and up to 5 tokens per account.
- Files are matched by path, so a renamed file shows as removed and new.
Questions
- What code is sent to Nodagentry?
- Only files the pull request changes that look like LangGraph, CrewAI or OpenAI Agents SDK code, in their before and after versions. They are parsed in memory to estimate cost and not stored. Nodagentry keeps when each token was last used and how many checks it ran today.
- Does it run my agent or call a model?
- No. Like the editor's importer it reads the code as text, finds each model step and its model, and multiplies estimated tokens by that model's price tier. Nothing is executed and no model is called, so a check costs no credits.
- Why didn't it comment on a pull request from a fork?
- GitHub doesn't give repository secrets to workflows started by pull requests from forks, so the workflow has no token and skips the check with a warning instead of failing.
- Can it block a merge when cost goes up?
- Yes. Set FAIL_ABOVE_PCT in the workflow, for example to 20, and the check fails when any file's estimated cost per run rises by more than that. Make the check required in branch protection to block the merge.
- Which plan includes it?
- The Monthly plan. While paid plans are in beta it is free for every signed-in account.
Next
Find the expensive step yourself: import LangGraph, CrewAI or OpenAI Agents SDK code into the editor and try a cheaper model on it.