Drift is a difference between your real infrastructure and the workspace’s state: someone changed a resource in the cloud console, a script edited a tag, or a resource was deleted by hand. Drift detection checks for it on a schedule, so you find out without anyone pushing a commit.
What a check compares
A drift check is a refresh-only plan. It reads every resource in the workspace’s state from your cloud provider and compares what it finds with what the state says.
- It doesn’t compare your configuration with your infrastructure, so a newer
module version, a data source such as “latest AMI”, or
timestamp()never shows up as drift. - It never applies, never writes state, and never locks state.
- It never posts a GitHub check or pull request comment.
The check appears in the run list with the kind Drift check. Its run page shows each resource that drifted, with the attribute-level differences, in the same plan view as any other run. A check that finishes reads Plan completed, whether or not it found drift.
Turn on drift detection
- Open the workspace and select the Drift tab.
- Choose a Frequency. For Daily and Weekly, choose the Hour (UTC). For Weekly, also choose the Weekday.
- Select Save.
When you first turn drift detection on, the first check starts within about 5 minutes. The tab then shows when the next check is due.
Saving the same settings again doesn’t move the next check. Changing the frequency, hour, or weekday moves the next check to the new time; it doesn’t start one right away.
To stop checking, select Turn off. The workspace’s drift status is hidden until you turn drift detection on again. Past drift checks stay in the run history.
Frequency and timing
| Frequency | When checks run (UTC) |
|---|---|
| Every 6 hours | 00:00, 06:00, 12:00, and 18:00 |
| Every 12 hours | 00:00 and 12:00 |
| Daily | The hour you choose |
| Weekly | The hour you choose, on the weekday you choose |
All times are UTC. Each workspace adds its own fixed minute, from 0 to 59, so a check starts up to 59 minutes after the hour shown, at the same minute every time. This spreads checks across your organization rather than starting them together. The most frequent schedule is every 6 hours. A new schedule defaults to Daily.
What a check uses
Configuration. A check uses the configuration of the workspace’s latest
successful plan-and-apply run. If the workspace has never applied through
StackShip (for example, you moved its state in with tofu state push), it
uses the configuration of its latest plan that finished. Speculative plans
from pull requests are never used. Until the workspace has one of these, a
scheduled check is skipped and waits for its next scheduled time.
Variables and credentials. A check gets the same workspace variables, secrets, and cloud credentials as a plan-only run in the workspace. If the workspace has no cloud credentials for plans, every check fails; see cloud access.
Checks never block your runs
A drift check doesn’t count toward the workspace’s or organization’s active
runs, doesn’t lock state, and doesn’t hold the workspace. A push, a tofu
CLI run, or an API run starts exactly as it would with no check running.
When the workspace is busy with another run, StackShip skips the scheduled check and tries again about 15 minutes later, rather than making your run wait. Only a limited number of drift checks run at once, both in your organization and in each execution region; a check over either limit also tries again about 15 minutes later.
Drift status
While drift detection is on, the workspace shows a drift status on its overview and in the workspace list.
| Status | Meaning |
|---|---|
| No drift | The latest check found no differences, or the current state was written by a StackShip apply, which reads real infrastructure as it runs |
| Drift detected | The latest check found differences. The badge shows how many resources would be added, changed, and destroyed to bring state back in line, such as +0 ~2 −1 |
| Check failed | The latest check stopped before its plan finished, for example because the workspace has no cloud credentials. Open the check to read the error |
| Checking | A check is running |
| Not checked yet | No check has finished against the current state yet |
A check only counts against the state it read. When an apply or a
tofu state push writes new state, the status starts over: No drift
after a StackShip apply, Not checked yet after a state push, until the
next check finishes.
Why a check didn’t run
When a scheduled check is skipped, Not checked yet and the Drift tab show why:
| Reason | What it means |
|---|---|
| Runs once the workspace has a plan or apply | There’s no configuration to check yet. The check waits for its next scheduled time. Push to the tracked branch or run tofu plan |
| Waiting for the active run to finish | Another run, state upload, or recovery is in progress. The check tries again in about 15 minutes |
| Waiting for capacity | Your organization, or the workspace’s execution region, is already running as many drift checks as it can at once. The check tries again in about 15 minutes |
| Runs are paused | StackShip isn’t starting new runs right now. The check tries again in about 15 minutes |
| Cloud credentials need attention | A credential profile on this workspace isn’t verified, or isn’t allowed to plan. Verify the profile or allow it to plan; the check tries again at its next scheduled time |
Email when drift appears
When a workspace drifts, StackShip emails every owner and admin in your organization with a verified email address. Deactivated users get no email. The subject is Drift detected in followed by the workspace name. The email says how many resources changed and has a Review drift link to the check. It never includes resource addresses or attribute values.
Each person gets one email per drifted state. While the workspace stays drifted against the same state, later checks send nothing. Once an apply or state push changes the state, new drift sends a new email. There’s no way to turn these emails off yet.
Revert drift
When the status is Drift detected, the workspace overview shows Revert drift. It starts a normal plan-and-apply run from the same configuration the check used (not the latest commit on the tracked branch) and opens it.
The plan shows what the apply would change to undo the drift. Review it, and approve it like any other run; see run approvals. If the workspace is busy with another run, Revert drift shows why the run couldn’t start.
To keep a change someone made outside StackShip instead, update your configuration to match and push it.
Permissions
| Role | Can do |
|---|---|
| Member | See drift status and the Drift tab settings; select Revert drift if they can start runs |
| Admin or owner | Everything a member can, plus turn drift detection on, change its schedule, and turn it off. Approve the revert run |
Changes to a schedule are recorded in the audit log as
workspace.schedule.updated and workspace.schedule.deleted.
Manage drift detection with the API
| Request | Description |
|---|---|
GET /v1/workspaces/{workspace_id}/schedule |
Read the schedule, with next_run_at and last_skip_reason. Returns 404 when drift detection is off |
PUT /v1/workspaces/{workspace_id}/schedule |
Turn drift detection on or change it. Send cadence (every_6_hours, every_12_hours, daily, or weekly), hour_utc (0–23, for daily and weekly), and weekday (0–6, Sunday is 0, for weekly). Leave the others null. Requires an Idempotency-Key |
DELETE /v1/workspaces/{workspace_id}/schedule |
Turn drift detection off. Requires an Idempotency-Key |
GET /v1/runs?kind=drift_check |
List drift checks. An unknown kind returns 400 RUN_LIST_KIND_INVALID |
curl -X PUT https://api.stackship.run/v1/workspaces/ws_9x2p/schedule \
-H "Authorization: Bearer $STACKSHIP_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"cadence": "daily", "hour_utc": 6, "weekday": null}'Every workspace object includes drift: the status, checked_at,
run_id of the check, its configuration_version_id, the add, change,
and destroy counts, and skip_reason. It’s null while drift detection is
off. To revert drift through the API, send POST /v1/runs with
kind: "plan_and_apply" and that configuration_version_id.
You can’t start a drift check with POST /v1/runs; only a schedule starts
them. AI agents can read and set schedules too; see
MCP server.