Run a query as a campaign
Run a query as a campaign
The default Run a query flow is live-only: a laptop that closed its lid mid-query contributes nothing. Launch as campaign sends the same osquery statement through the campaigns pipeline instead, so offline hosts catch up when they reconnect, and the run leaves a durable record you can come back to later. It’s the right tool when the SQL isn’t IOC-shaped but you still want offline tolerance.
What it is
A campaign-mode query is raw operator-written osquery, dispatched to every targeted host (online or not), with the full offline-tolerant lifecycle: assignment ledger, reconnect catch-up, coverage card, pending hosts, late responses, Stop button, audit trail. The data that comes back is the same rows you’d get from a live run — there’s no IOC matching, no alerts, just osquery results stamped with the host that produced them.
The campaign shows up on the Campaigns page
mixed in with IOC hunt campaigns; the Kind column distinguishes
them (osquery vs ioc_hunt). The detail view is shaped to match the
data — instead of a Frozen IOCs card and a Late Matches card, it shows
a Frozen SQL card (the literal query text it was launched with)
and a Late Responses card (the hosts that answered after the live
window closed).
When to use it
Three patterns:
- Incident response on an intermittently-connected fleet. “Every
host that has ever loaded the file
C:\evil.dll, get back to me even if you’re a remote laptop that won’t reconnect until tomorrow.” The live-only path under-reports because the host you care about is offline at dispatch. Campaign mode picks them up on reconnect. - Compliance evidence trails. “On day Y we asked every host about X.” A campaign is a durable record of that question and every answer that came back — useful for auditors who want to see proof of completion, not just a point-in-time snapshot.
- Long-running fleet sweeps. A query that doesn’t need to finish in the SSE window. Launch it, walk away, come back to the campaign detail page later to see the late responses.
For a one-shot spot-check (“listening ports on host X right now”), the default live-only Run a query flow is still the right tool. Campaign mode adds overhead — an assignment row per host, a durable record — that’s wasted on a query you don’t care about ten minutes from now.
How to use it
On the Queries page, near the Run query button, tick Launch as campaign (offline-tolerant). The toggle reveals a Live window (minutes) input (default 10, range 1–1440) and relabels the button to Launch as campaign.
- Type or paste your SQL.
- Pick a target (Fleet / Single host / By OS) as usual.
- Tick Launch as campaign.
- Adjust the Live window if 10 minutes isn’t right. The live
window is the period during which the campaign counts against the
MaxConcurrentHuntsconcurrency cap — short windows free the slot sooner for the next launch. - Click Launch as campaign. A toast confirms how many hosts the
campaign was dispatched to, and the page navigates to the
campaign’s detail view at
/campaigns/<id>.
From there, watch the coverage card fill in, drill into pending hosts, read late responses, or stop the campaign. The full campaign-detail UX is documented in Reading a campaign; the osquery-specific cards are called out below.
Launching from a saved query
The Saved queries rail’s per-row action menu has a Launch as campaign entry. The modal pre-fills the SQL from the saved query and the target from the current Queries-page target picker — by default the editor’s target wins, so you can re-target a saved query without rewriting it. Tick Use saved query’s target to have the saved query’s stored scope override instead.
If the saved query’s stored scope is OS-filtered (os_in), launching
as a campaign is rejected up front with the modal error:
Campaigns don’t yet support OS-filtered targets — pick Fleet or specific hosts instead.
See Limits below for the rationale.
How it differs from a hunt
Both paths use the campaign pipeline. The difference is what the campaign carries and what it produces:
| Hunt (IOC-driven) | Run as campaign (osquery) | |
|---|---|---|
| Source | A frozen IOC set; the IOC compiler turns it into SQL | Operator-written SQL (free-form or saved query) |
| Per-row output | Matches against the frozen IOC snapshot | Raw osquery rows |
| Alerts produced | Yes — every match creates a source='campaign' IOC alert | No — there’s nothing to match against, just data |
| Detail page result card | Late Matches (IOC type, value, host, time) | Late Responses (host, row count, time, “view rows”) |
| Frozen card | Frozen IOCs (the snapshot) | Frozen SQL (the literal query text) |
For indicator-driven sweeps — “every host that touched this SHA256” — the hunt path is purpose-built and ranks matches for triage. Use this page’s flow for the investigations that aren’t IOC-shaped.
Per-host row cap and the truncated badge
A live-only query has one server-side row cap (default 50,000) that applies to the entire result set. A campaign query that fans out across a fleet would silently lose late-arriving hosts under that shared cap, so the cap is per host instead: each host’s rows are preserved up to the cap independently, and a host that hits the cap gets a rows truncated badge in the Late Responses card.
If you see truncated badges, the query is producing more rows per host than the cap allows — narrow the query or split the work.
Aggregate cap auto-stop
To bound worst-case storage growth (4,000 hosts × very large queries
can run away), each campaign carries a per-campaign aggregate row
counter. When the aggregate cap fires (default 10,000,000 rows), the
campaign auto-stops with reason auto_stopped_aggregate_row_cap and
the detail page shows an amber banner explaining what tripped. Rows
already collected are kept; new responses are cancelled. The default
is the panic cutoff, not the steady-state — ask your operator to tune
it from production data if it fires routinely.
Stop semantics
Same as IOC-hunt campaigns. Admins see a red Stop Campaign button on the detail page; clicking it opens the stop modal.
- Reason — free-text up to 1,000 characters, shown alongside the stopped-at timestamp in the header strip.
- Discard in-flight results — by default Mimir cancels pending assignments but accepts responses already on the wire. Tick this to also reject responses that arrive after stop.
A stopped campaign keeps its row, its frozen SQL, and the responses already collected. Nothing is deleted.
The full stop flow is documented in Reading a campaign.
Promoting a campaign query to a saved query
A campaign launched from free-form SQL can be saved for re-launch later. Open the campaign detail view, copy the SQL from the Frozen SQL card, paste it into the editor on the Queries page, and follow the regular Save flow. A saved query created this way can be re-launched as a campaign from the saved queries rail’s per-row action menu, closing the loop.
Limits
Three deliberate v1 restrictions:
- OS-filtered targets aren’t supported. Campaigns target Fleet
or a specific host list. A saved query whose stored scope is
os_in(e.g., “Windows only”) is rejected when you try to launch it as a campaign — switch the scope to Fleet or the equivalent host list instead. This is a known gap; OS-filtered campaigns are tracked as a follow-up. - No result-set alerts. A campaign osquery query produces data, not alerts. If you need an alert when a result set matches some shape (“any host returned > 1000 rows”), promote the query to a scheduled pack instead, or use a hunt with an IOC.
- Retention is operator-managed. A stopped campaign holds its raw rows until an operator drops them. The per-host and aggregate row caps bound worst-case growth, but there’s no automatic cleanup of stopped campaigns in v1.
Permissions
Launching as a campaign requires the admin role — the same gate
as the live-only ad-hoc queries endpoint. Fleet-wide campaigns
(target_type='all') additionally require either the admin role or
the can_launch_fleet_hunts capability, matching the hunt path’s
fleet gate.
Like all campaigns, the Launch as campaign toggle is gated by the
runtime feature flag offline_campaigns_enabled. When the flag
is off, the toggle is disabled with the tooltip:
Enable Campaigns on the Campaigns page to make queries offline-tolerant.
See Launching a campaign for the flag toggle and audit attribution.
Troubleshooting
The toggle is disabled and the tooltip says “Enable Campaigns…”.
The offline_campaigns_enabled runtime flag is off. Open the
Campaigns page and click Enable
Campaigns; subsequent launches will be allowed. Flipping the
flag requires the admin role — non-admins will see the button
but the click returns 403. Ask an admin to enable it instead.
The launch returns max_concurrent_hunts_reached. Too many hunts
and campaigns are active in their live windows. The cap is fleet-wide
(or per-user, depending on the PerUserHuntIsolation setting). Wait
for one to finish its live window, or stop one early.
The launch returns blocked_sql. The SQL touched a deny-listed
osquery table at a multi-host scope. The deny-list applies at three
surfaces — launch, live-drain, and reconnect — so the same SQL that
runs fine against a single host is rejected against the fleet. Narrow
the target or rewrite the query.
The launch returns capability_required. You tried to launch a
fleet-wide campaign without the admin role or the
can_launch_fleet_hunts capability. Pick a specific host list, or
ask an admin to grant the capability.
A host’s row in Late Responses shows the truncated badge. That host produced more rows than the per-host cap. The rows you have are good; there were more rows on that host that didn’t make it into the campaign. Narrow the query or split the scope.
An amber banner says the campaign auto-stopped at the aggregate cap. The campaign produced more total rows across the fleet than the aggregate cap allows. Rows already collected are kept; new responses are cancelled. Re-launch with a narrower query.
Where to next
- Run a query — the default live-only flow, the editor, and the target picker.
- Saved queries — save and share queries; launch them as campaigns from the per-row action menu.
- Launching a campaign — the campaign pipeline as a whole; the feature flag toggle and audit attribution.
- Reading a campaign — the detail view, the Frozen SQL and Late Responses cards, the stop flow.
- Launch a hunt — the IOC-driven campaign path, for the investigations that are IOC-shaped.