Configuration reference
Every Action input and output, every .diffcost.yml key, and which setting wins when several are set.
Where settings come from#
From highest to lowest precedence:
- Dashboard settings, with an API key: thresholds, the BigQuery pricing model and Cost per TiB. The dashboard's pricing model and rate always win over the input and the file, so choose Editions in the dashboard, not only in the workflow. A dashboard threshold (for the repository, or the org-wide threshold) replaces the file's thresholds.
.diffcost.ymlon the base branch.- Action inputs.
If the dashboard settings can't be fetched (the API key is rejected, the run is rate-limited, or the DiffCost service can't be reached), the run uses the file and the inputs. If the DiffCost service then accepts the results, its verdict (dashboard thresholds and your plan) still decides the status.
ignored_paths come only from .diffcost.yml, so file paths never reach the DiffCost service.
Action inputs#
| Input | Default | Description |
|---|---|---|
github_token |
${{ github.token }} |
Token that posts the PR comment; the job needs pull-requests: write. The default token, a GitHub App's token or a personal access token all work: DiffCost updates the comment it posted with that token. |
sql_paths |
Auto-detect | Glob for SQL files, such as queries/**/*.sql; separate several globs with commas. * also matches /, so queries/*.sql matches nested files too, and **/ matches zero or more directories. Without it: the dbt project if the base branch has one, otherwise **/*.sql. Ignored when manifest_path is set. |
manifest_path |
Auto-detect | Path of the manifest.json that dbt compile writes in this run. Default: target/manifest.json next to the base branch's dbt_project.yml. See dbt projects. |
gcp_credentials |
— | Raw JSON of a service account key (starts with {; not base64), or the path of a credentials file in the workspace. Leave empty with Workload Identity Federation. |
gcp_project |
The key's project_id |
Google Cloud project that runs the dry-run jobs. Set it when you use Workload Identity Federation. |
api_key |
— | DiffCost API key. On every plan it stores results in the dashboard and applies the dashboard's pricing settings; thresholds, Slack alerts and trends need Starter, Team or the trial. When it's empty (no key, or a missing secret), no status is posted and the comment says "No DiffCost API key". |
base_ref |
The PR's base | Compare against this instead of the PR's base (how DiffCost picks the base): a branch name (looked up as origin/<branch>, then as a local branch), a refs/heads/… or refs/remotes/… ref, or a 40-character SHA. Never a tag. |
cost_per_tb |
6.25 |
USD per TiB for on-demand pricing. With an API key, the dashboard's Cost per TiB is used. An invalid value warns, and 6.25 is used. |
bq_pricing_model |
on_demand |
on_demand shows dollars; editions shows bytes scanned, because slot-based cost depends on your reservation. With an API key, the dashboard's pricing model is used. |
comment_on_clean |
false |
Post a comment even when the PR changes no SQL. DiffCost also comments when no status could be posted, and replaces an earlier report when the latest push changes no SQL (Other comments). |
config_path |
.diffcost.yml |
Path of the config file. DiffCost always reads it from the base branch. |
fail_on_error |
false |
Fail the step when DiffCost can't finish. See What fail_on_error covers. |
send_author |
false |
Send the PR author's GitHub login (from the pull request event, else the user who triggered the run) for cost attribution. Merge-queue runs send no author. You can also set it in .diffcost.yml. |
What fail_on_error covers#
With fail_on_error: true, the step fails when:
manifest_pathorconfig_pathis not a relative path without.., orbase_refis not a valid branch name, ref or SHA;- on a merge-queue run, the queue branch has no PR number;
.diffcost.ymlcan't be read or parsed, or has an invalid threshold;- a git or dbt manifest error stops the analysis;
- the DiffCost service can't be reached, doesn't accept the results, or answers with something that isn't a DiffCost result (for example a proxy's page).
The step doesn't fail when the DiffCost service accepts the results but can't post the commit status (for example, the GitHub App is missing). DiffCost reports this in the comment and the log. Make diffcost/cost-check a required check so the PR waits in that case.
With false (the default), the step always passes, and DiffCost reports problems in the log, the PR comment and the job summary.
Action outputs#
| Output | Description |
|---|---|
total_cost_delta |
Net cost change in USD per run, across costed queries (positive = increase). |
monthly_cost_delta |
total_cost_delta × default_runs_per_month (a projection). |
max_query_cost_delta |
Largest single-query increase in USD per run: what absolute thresholds compare against (0 if no query got more expensive). |
max_regression_pct |
Largest single-query percentage increase. |
files_analyzed |
Number of changed SQL files or models analyzed, plus grouped Cost unavailable items (non-model dbt files, SQL outside the analyzed dbt project). |
items_unknown |
Number of queries with an unknown cost (excluded from totals). A dbt model marked Not costed counts only when it blocks. |
total_bytes_before / total_bytes_after |
Bytes scanned per run, base vs PR. |
scan_multiplier |
PR bytes ÷ base bytes (such as 4.20). |
blocked |
true only if the cost policy was exceeded and DiffCost posted the failing diffcost/cost-check status. |
policy_exceeded |
true if the cost policy was exceeded, enforced or not. See When policy_exceeded is evaluated. |
analysis_status |
ok (the analysis ran, even if no query could be costed), no_changes (no SQL changes, or not a pull request), or error (DiffCost could not analyze the PR; the other outputs are then 0 or false, and scan_multiplier is 1.00). |
Give the DiffCost step an id to use them in later steps; Monitoring has examples.
When policy_exceeded is evaluated#
- With an API key on Starter, Team or the trial.
- Whenever an API key is set but the dashboard settings couldn't be fetched (the key was rejected, the run was rate-limited, or the DiffCost service couldn't be reached). DiffCost then checks the
.diffcost.ymlthresholds on the runner, on any plan. If the DiffCost service then accepts the results, its verdict (dashboard thresholds and plan) replaces the local one, so a Free organization seestrueonly when the service also can't take the results.
Otherwise it is false: without an API key, and on the Free plan.
.diffcost.yml#
Place it in the repository root (or set config_path). DiffCost reads it from the PR's base commit, so editing the file in a PR doesn't change that PR's cost policy. After you merge an edit into the base branch, it applies to each open PR's next run triggered by a push (or by Update branch in GitHub); re-running an earlier job keeps the old file. To change the policy for a PR that's already blocked, merge the change in a separate PR first.
thresholds:
absolute: 5.00 # USD per run, worst single query
relative: 200 # percent, worst single query
# Runs per month used for the "≈$X/mo" projection (default 30)
default_runs_per_month: 30
ignored_paths:
- "tests/**/*.sql"
- "migrations/**/*.sql"
# Slack incoming webhook (paid plans), alerted when these thresholds are exceeded.
# GitHub does not expand ${{ }} inside repository files: DiffCost reads this variable
# from the DiffCost step's `env:` (see below).
slack_webhook: ${{ SLACK_WEBHOOK }}
# Optional overrides of the matching inputs
# bq_pricing_model: on_demand
# fail_on_error: false
# send_author: false
| Key | Description |
|---|---|
thresholds.absolute |
Block when any one query's cost goes up by more than this many USD per run (0 to 1,000,000). A new query counts with its full cost. |
thresholds.relative |
Block when any one query's cost goes up by more than this percentage (0 to 100,000). Only queries whose base cost (the Current column) is above $0 count. |
default_runs_per_month |
Runs per month for the monthly projection, 1 to 100,000 (default 30). |
ignored_paths |
A list of globs of repository-relative paths DiffCost skips, for example SQL for another database. Globs work as in sql_paths: every directory in a glob must match, so **/legacy/** skips legacy/a.sql and jobs/legacy/b.sql but not models/orders.sql. |
slack_webhook |
Slack incoming webhook, alerted when these thresholds are exceeded. See Slack alerts. |
bq_pricing_model, fail_on_error, send_author |
Override the matching inputs. With an API key, the dashboard's pricing model still wins. |
Thresholds in the file take effect only with an API key on a paid plan or the trial, and only when no dashboard threshold applies to the repository (see Which threshold applies). When the dashboard settings can't be fetched (key rejected, rate-limited or service unreachable), the Action also checks them on the runner, on any plan; that local check never posts a status. If the DiffCost service then accepts the results, its verdict applies (see When policy_exceeded is evaluated).
Passing secrets to the file#
GitHub doesn't expand ${{ }} in repository files. DiffCost replaces ${{ NAME }} in the slack_webhook value with the environment variable NAME from the DiffCost step (no other key is expanded), for these names only: SLACK_WEBHOOK, SLACK_WEBHOOK_URL and DIFFCOST_SLACK_WEBHOOK.
- uses: diffcost/action@v1
with:
api_key: ${{ secrets.DIFFCOST_API_KEY }}
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
Invalid values#
DiffCost ignores invalid values; most of them also produce a warning.
These make the cost policy weaker than configured. They appear in the log and in the cost report as "⚠️ Cost policy: …", and fail the step when fail_on_error: true:
- the file can't be read, isn't valid YAML, or isn't a mapping;
thresholdsisn't a mapping; - a threshold that isn't a number or is outside its range (DiffCost drops it).
These only warn in the step's log, even with fail_on_error: true:
default_runs_per_monthoutside 1 to 100,000 (DiffCost uses 30);- a
slack_webhookthat isn't anhttps://hooks.slack.com/URL, or that resolves to an empty value (no Slack alert is sent); - an invalid
bq_pricing_model(DiffCost useson_demand, even over the input).
These are ignored without a warning: an ignored_paths that isn't a list, and a fail_on_error or send_author that isn't true or false.
Dashboard settings#
With an API key, the dashboard's Settings page sets the BigQuery pricing model, the Cost per TiB and the thresholds, and these win over the file and the inputs. See Using the dashboard.
Something missing or wrong? Email support@diffcost.dev.