Audit event reference¶
Every action name cloop writes to the event_type column of
audit_events, generated from the registry the emitters reference. Do not
edit this file: it is rendered by tests/docs/audit_events_test.go from
pkg/auditaction, and a CI check fails when it stops matching. Regenerate
with make docs-audit.
For what the trail is and how it is protected see the security model; for the endpoints that read it see the HTTP API reference.
How to read this¶
audit_events is cloop's legal record: one append-only, hash-chained row per
mutation that actually changed something. It is distinct from the events
table, which is a UI journal that may be lossy and is prunable at will. A row
carries an actor, an action, an entity type and id, and a JSON payload.
The action is the value of event_type. It is the field a detection rule
keys on, and it is stable: a name in this list will not be renamed, because
renaming it would orphan rows already sealed into a chain that cannot be
rewritten. A superseded action is marked deprecated here and keeps being
emitted until retention has passed over the last row carrying it.
The entity is the value of entity_type, and it says which id space
entity_id is in. Filtering a subject's whole history means filtering on
both.
Payload keys are conditional. An emitter omits what it does not know, so the keys listed for an action are what a consumer may expect to see, not what every row carries. New keys may be added to a stable action without notice — a consumer must tolerate that — but no listed key is removed or repurposed.
Stability is a contract with consumers, not a measure of code quality:
| Marker | Means |
|---|---|
stable |
Safe to key a paging alert on. The name and the listed payload keys will not change. |
beta |
The rows are real, but the name or payload may still change while the surface emitting them settles. Key a dashboard on it, not a pager. |
deprecated |
Still emitted, replaced by something else. The note says by what. |
Exporting¶
cloop audit export writes these rows as JSONL or CEF for a SIEM, and
cloop audit verify checks the hash chain. Both are documented under
commands. Note that the trail exists per project and in the
control plane, with separate chains — a row emitted to one is not visible in
the other.
Who may read these¶
Reading all 256 of the actions below requires the audit.read permission, held by admin.
The trail is one table behind one pair of admin-only endpoints, so the permission does not vary by action today. It is recorded per action anyway, because the day one family needs narrower reads than the rest, the registry has to be able to say so without a schema change — and because a reader is better served by a column that says "the same everywhere" than by prose that leaves them guessing whether it varies.
Which database an action is recorded in¶
audit_events is not one table. It exists in the hub's own control-plane
state.db and in every project's .cloop/state.db, and each copy carries its
own independent hash chain. A row does not say which database it came from, so
verifying one chain proves nothing about the other: both stay internally
consistent whether or not an event landed in the right one.
This matters when answering a question about one project. The plan's own life is
in the project's chain; the executor that ran it, the image policy that admitted
it, the workspace that was fetched for it and the credentials it held are in the
hub's. Use cloop hub audit list to read both at once — it labels every row with
its source — and cloop hub audit verify to verify both chains rather than
whichever one happened to be opened.
| Home | Meaning | Actions |
|---|---|---|
control-plane |
the hub's own state.db | 223 |
project |
the project's .cloop/state.db | 29 |
either |
whichever chain the decision was scoped to | 4 |
Recorded in the project's .cloop/state.db: config.set, feature.create, feature.pr_open, feature.remove, requirement.approve, requirement.check, requirement.create, requirement.delete, requirement.draft, requirement.import, requirement.prove, requirement.update, requirement.verdict, run.adopt_requested, run.cap_paused, run.cap_resumed, run.firewall_denied, run.reexecuted, state.save, step.append, task.attachment_delete, task.delete, task.dispatch, task.finish, task.gate_approve, task.quarantine, task.quarantine_release, task.status, task.upsert.
Recorded in whichever chain the decision was scoped to: audit.gap, audit.restored, authz.denied, authz.granted.
Everything else is recorded in the hub's own state.db.
Actions by family¶
256 actions in 53 families. Every action is listed: this section is the whole
vocabulary of the event_type column.
task.* (9) · run.* (5) · feature.* (3) · requirement.* (9) · knowledge.source.* (7) · knowledge.* (2) · step.* (1) · state.* (1) · config.* (1) · executor.* (19) · forwarder.* (12) · executor_pool.* (12) · workspace.* (2) · sandbox.* (1) · sandbox.attach.* (3) · secret.* (15) · secret.lease.* (1) · lease.* (4) · github_app.* (1) · egress.* (8) · gitproxy.* (9) · kubeguard.* (6) · ci.* (1) · ci.session.* (5) · ci.exchange.* (2) · ci.relay.* (2) · ci.rule.* (3) · ci.config.* (1) · model_relay.session.* (4) · model_relay.* (5) · authz.* (2) · api_token.* (6) · static_token.* (1) · session.* (10) · device.* (3) · request.* (2) · role_binding.* (3) · quota.* (4) · resource_ceiling.* (2) · sealing_key.* (5) · hub_ca.* (8) · oidc.* (2) · telemetry.* (1) · notifications.* (1) · disk.* (1) · stt.credential.* (2) · user.* (17) · scim.* (16) · project.* (3) · project.member.* (4) · team.* (5) · audit.* (2) · audit.forward.* (2)
task.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
task.attachment_delete |
task |
project | stable | A person deletes a file attached to a task, from the dashboard or with cloop task attachments remove. |
task.delete |
task |
project | stable | A task disappears from the plan between two saves, or is deleted outright. |
task.dispatch |
task |
project | stable | A task is handed to an executor, recording where it will run and what it was given. |
task.finish |
task |
project | stable | A dispatched task reaches a terminal outcome — done, failed, skipped, timed out, or aborted. |
task.gate_approve |
task |
project | stable | A person approves a human review gate in the task list, letting the run continue past it, or pre-approves a task that requires approval. |
task.quarantine |
task |
project | stable | Failover marks a task as a suspected node killer: two or more distinct executors went unreachable while it was running. |
task.quarantine_release |
task |
project | stable | An explicit reset returns a suspected node killer to pending, clearing its mark and the losses behind it. |
task.status |
task |
project | stable | Somebody flips a task's status by hand, rather than the orchestrator moving it. |
task.upsert |
task |
project | stable | A task is created, or a saved task's audited fields differ from the ones already stored. |
Payload keys:
task.attachment_delete—id,attachment_id,name,size,sha256task.delete—idtask.dispatch—task_id,title,project,run_id,executor_id,executor_kind,isolation,requested_image,pinned_image,image_digest,image_provenance,verified_digest,spec_sha256,setup_sha256,lease_ids,recorded_bytask.finish—task_id,outcome,title,project,run_id,executor_id,executor_kind,isolation,reason,started_at,completed_at,duration_ms,abort_class,abort_cleared,fail_count,write_back_committask.gate_approve—task_id,title,kind,waiting,viatask.quarantine—task_id,title,kind,nodes,reasontask.quarantine_release—task_id,kind,nodes,marked_at,viatask.status—id,old_status,new_status-
task.upsert—id,title,status,priority,role,description -
task.attachment_delete— Attaching is not audited: a run's files are recorded in the project's event journal, which names the run. A file deleted with its task is covered by that task'stask.delete. task.dispatch— This and task.finish are what make "where did this task run, and what credentials did it hold" answerable from audit_events alone, without joining the executor tables. image_digest is the digest pinned_image names. A run carried out as a seed — on a Kubernetes Pod or a device — writes its own rows into a copy of the project that never comes back, so the hub writes them when it merges the run's outcome, recorded_by "hub" (Task 20457).task.finish—outcomecarries the terminal status;reasoncarries why, when there is one.task.gate_approve—kindishuman_gatefor a review gate and empty for a task.waitingis true when a run had stopped at the gate.vianames where it was approved: the dashboard orcloop task gate approve.task.quarantine—nodeslists each executor that went down under the task with when it went unreachable. A marked task runs again only after an explicit reset, recorded as task.quarantine_release.task.quarantine_release—vianames the reset — the dashboard,cloop task reset,cloop task bulk reset; the actor is who pressed it.task.upsert— Emitted from the diff SaveState computes inside its own transaction, not from the plan — a byte-identical re-save produces no row. Before that diff existed this action was 99.7% of a 1.09M-row table.
run.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
run.adopt_requested |
plan |
project | stable | Somebody asks a project's running host-process run to adopt the hub's build at its next task boundary. |
run.cap_paused |
plan |
project | stable | A Claude Code subscription cap stops the run before it dispatches its next task — a configured cap, or a window used up while budget.block_extra_usage holds the run out of extra usage (cap 100). |
run.cap_resumed |
plan |
project | stable | The hub restarts a cap-paused run after its window rolled over, without a human — or, for the extra-usage pause an older build recorded as a spent budget, once the usage API shows the window open (no resumes_at). |
run.firewall_denied |
plan |
project | stable | A run ends whose sandbox firewall refused connections: one row per deny path that refused any, with the totals. |
run.reexecuted |
plan |
project | stable | A run replaces its own image with a newer build at a task boundary, keeping its process. |
Payload keys:
run.adopt_requested—project,request_id,pid,run_build,hub_build,behindrun.cap_paused—project,windows,utilization,cap,resumes_at,detailrun.cap_resumed—project,paused_detail,resumes_at,resumed_at,acted_for,acted_for_basisrun.firewall_denied—project,run_id,executor_id,rule,why,count,destinations,top,first_seen,last_seen,fingerprint-
run.reexecuted—project,run_id,pid,from,to,reason,reexecs -
run.adopt_requested— Gated like starting a run. The request names the run's process, so it is acted on by that run or by none; what the run then did is its run.reexecuted row, or a refusal in the project's journal. run.cap_paused— Distinct from state.save's bare "paused" status because a cap is the one pause that ends by itself:resumes_atis the window's reset from the OAuth usage API, and it is what run.cap_resumed is later matched against.run.cap_resumed— The only action recording work that a machine started on its own initiative, so "who restarted this project" stays answerable from the trail alone. On a hub with single sign-onacted_fornames whom the run acts for, on their own Claude account, andacted_for_basishow that was decided:starter, the person the resumed run was started for as recorded at its dispatch (noacted_forwhen it was started acting for no one),owner, the project's owner, for a run older than that record, orunknown, nobody nameable, on an executor that needs no Claude home. A resume that could not act for them is not restarted, and leaves no row.run.firewall_denied— Aggregated per run and rule (Task 20437), never per packet:countis the refusals the sandbox's packet log reported — rate-limited to ten a second per deny path, so a flood is at least this many —destinationshow many distinct destinations,topup to eight of them asdst:port/proto.ruleis the deny path's tag (default,deny:<prefix>,meta:<prefix>,block:<prefix>,xlat:<prefix>), orunitemisedfor what the run's capped aggregate counted without itemising;whyis the hub's sentence for it. Each destination is also anetwork_blockedrow in the run's history. Absent for a run on Kubernetes, whose NetworkPolicy has no portable log, and for one on a device whose agent predates protocol v24.run.reexecuted— Written by the new image. Together with task.dispatch it answers "which build ran this task": a run's tasks before this row ran onfrom, after it onto.
feature.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
feature.create |
feature |
project | stable | A feature — a git worktree of the project with its own task list — is created. |
feature.pr_open |
feature |
project | stable | A feature's branch is pushed and a pull request for it is opened or updated. |
feature.remove |
feature |
project | stable | A feature's worktree, and with it its task list and history, is removed. |
Payload keys:
feature.create—slug,branch,base,path,auto_evolve,innovate,tasksfeature.pr_open—slug,number,url,base,existing,automatic-
feature.remove—slug,branch,branch_deleted,force -
feature.create— Filed in the parent project's trail, not the feature's: the feature's own trail starts with its first run, and "which features did this project have" is a question about the parent. feature.pr_open— automatic is true when the hub opened it on its own because the feature completed with auto-PR set, so a pull request nobody clicked for is still attributable.
requirement.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
requirement.approve |
requirement |
project | stable | A requirement becomes approved: a draft approved in the dashboard or with cloop requirements approve, a requirement edited to approved, or a spec file import that approves it. |
requirement.check |
requirement |
project | stable | A check run ends — its tests ran on the project's executor, or it failed or was cancelled. |
requirement.create |
requirement |
project | stable | A requirement is added to the project, from the dashboard or with cloop requirements add. |
requirement.delete |
requirement |
project | stable | A requirement, and the history of its checks and proofs, is deleted. |
requirement.draft |
requirement |
project | stable | The model drafts requirements from the project's goal or a specification, at a person's request — the dashboard's Draft requirements, or cloop requirements draft. |
requirement.import |
requirement |
project | stable | The project's requirements spec file (.cloop/requirements.yaml) is merged into the hub — from the dashboard's Import, or with cloop requirements import. |
requirement.prove |
requirement |
project | stable | A run of red-green proofs ends — the requirements' tests ran at the base and on the change, on the project's executor, or the run failed or was cancelled. |
requirement.update |
requirement |
project | stable | A requirement's title, description, priority, tags, tests or status change. |
requirement.verdict |
requirement |
project | stable | A person records a requirement's verdict by hand — fulfilled or unfulfilled, with a note. |
Payload keys:
requirement.approve—id,title,previous_status,via,priority,tests,fingerprint,approved_byrequirement.check—run_id,state,source,requirement_ids,assess,executor_id,commit,dirty,fulfilled,unfulfilled,unverified,error,reason,task_idrequirement.create—id,title,priority,status,tests,fingerprint,draftedrequirement.delete—id,title,checks,proofs,unlinked_tasksrequirement.draft—source,sha256,bytes,ids,refused,executor_id,viarequirement.import—source,path,sha256,commit,added,restored,updated,deleted,resolved,in_sync,exports_pendingrequirement.prove—run_id,state,source,requirement_ids,executor_id,base,commit,outcomes,reason,task_idrequirement.update—id,title,changed,status,tests,fingerprint,previous_fingerprint-
requirement.verdict—id,verdict,note,fingerprint -
requirement.approve—viaisapprove,editorimport.testsis each test's command as it will now run: an approval is what lets a check, and the requirements gate, run them. An edit that also changes the definition writes arequirement.updaterow for that beside this one. requirement.check— One row per run, not per requirement: each requirement's verdict is a row of requirement_checks.assessis true when the model was asked about the requirements with no tests. A check the requirements gate ran when a task's agent reported it done hassourcegateand the task intask_id; it is 0 for every other check.requirement.create—testsis each test's command, as it will run.drafted, present only for a requirement the model drafted, names what from —goal,text,fileorstdin— and its status is then always draft.requirement.delete—unlinked_tasksare the tasks that implemented it, from which the deletion took it in the same transaction; each also gets atask.upsertrow under the same actor.requirement.draft— One row per draft, its entity iddraft, its actor the person who asked.sourceisgoal,text(pasted),fileorstdin;sha256andbytesdescribe that text, not its content.idsare the requirements stored, every one with the status draft — each also arequirement.createrow naming the source indrafted.refusedcounts the proposals that broke a bound or asked for another status.requirement.import— One row per import, its entity idspec.updatedlists each changed requirement with the fields that changed and its new and previous fingerprints;deletedhow many recorded checks went with each, and the tasks it was taken out of (unlinked_tasks, each also atask.upsertrow);resolvedhow each conflict was settled (fileorhub). An import applies nothing unless every conflict is settled, so a row is a whole import.sha256is the file's; a dashboard import was read from the project's checkout on its executor atcommit.requirement.prove— One row per run:outcomesmaps each requirement (R-<n>) to proven, vacuous, broken, inconclusive or error, and each proof is a row of requirement_proofs.baseis the commit the tests ran at before the change,committhe change's. The requirements gate's proof of a task's new or changed tests hassourcegateand the task intask_id.requirement.update—changednames the fields that differ. A newfingerprintmeans the verdicts recorded before it are about a requirement that no longer reads that way, and the dashboard shows them stale.
knowledge.source.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
knowledge.source.bind |
knowledge_source |
control-plane | stable | A knowledge source is made searchable from a project, or an existing binding's narrowing changes. |
knowledge.source.create |
knowledge_source |
control-plane | stable | An administrator connects an organisation knowledge source, or a person connects a personal one. |
knowledge.source.delete |
knowledge_source |
control-plane | stable | A knowledge source is removed, with every document, chunk and binding it had. |
knowledge.source.sync |
knowledge_source |
control-plane | stable | A person asks the hub to sync a knowledge source now — cloop knowledge sync, or POST /api/knowledge/sources/{id}/sync — and the sync starts. |
knowledge.source.test |
knowledge_source |
control-plane | stable | A person tries a knowledge source's connection — the dashboard's Test connection, POST /api/knowledge/test-connection — for a new source or an existing one: the hub lists one small page with the source's configuration and credential, and indexes nothing. |
knowledge.source.unbind |
knowledge_source |
control-plane | stable | A project stops being able to search a knowledge source: the binding is removed, or the project is. |
knowledge.source.update |
knowledge_source |
control-plane | stable | A knowledge source's name, configuration, credential reference, sync interval or enabled state changes. |
Payload keys:
knowledge.source.bind—source_id,binding_id,project,spaces,query,created,kind,name,scope,ownerknowledge.source.create—id,kind,name,scope,owner,config,signs_in_with,enabled,sync_interval_minutesknowledge.source.delete—id,kind,name,scope,owner,documents,chunks,bindingsknowledge.source.sync—id,kind,name,scope,owner,origin,memberknowledge.source.test—id,kind,name,scope,owner,config,signs_in_with,outcome,documents,withheld,reason,errorknowledge.source.unbind—source_id,binding_id,project,kind,name,scope,owner,reason-
knowledge.source.update—id,kind,name,scope,owner,changed,config,signs_in_with,enabled,sync_interval_minutes,resync -
knowledge.source.bind— The entity id is the source's.projectis the project's path.spacesandquerynarrow what the project can find: only documents in those space keys, and only documents the source's connector reported as matching the query at the last sync.createdis false when the row replaced an existing binding's narrowing. knowledge.source.create— The entity id is the source's id, which is never reused.scopeisorgorpersonal; a personal source'sowneris the person whose credential it reads with.configis the connector's configuration, which is refused if it holds anything shaped like a credential;signs_in_withis the secret store reference the hub signs in to the source with — a name, never the secret.knowledge.source.delete— The counts are what goes with it. Its bindings end here too: noknowledge.source.unbindrow is written for each. The source stops being searchable at this row; its documents and chunks are purged right after in bounded transactions, and what a purge cut short leaves is removed by the next one.knowledge.source.sync— Written when the sync takes the source's sync lease, so a request refused because another sync of the source is running writes none.originismanual;memberis the hub process that runs it. The syncs the hub-cluster leader schedules are its own routine work and are not audited: how each went is on the source (status, counts, duration and the last error, scrubbed of credentials).knowledge.source.test— The entity id is the source's for a test of an existing one, and empty for a source not yet saved.configandsigns_in_withare what was tried, which a form may not have saved.outcomeisokorfailed; a failure'sreasoniscredential(the secret could not be opened) orconnector(the remote refused or could not be reached), anderrorsays why, scrubbed of the credential and of anything else shaped like one. A configuration or credential reference the hub would refuse to store is refused before anything is tried, and writes no row.knowledge.source.unbind— A project removed from the hub takes its bindings with it — they are keyed by its path, and would otherwise serve whatever is registered there next — one row each,reasonsaying so.knowledge.source.update—changedlists the fields the request changed.resyncis true when a new configuration made the next sync start over from the beginning.
knowledge.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
knowledge.run.get |
knowledge_session |
control-plane | stable | A run reads one document of its project's knowledge with its knowledge session — by its id, its URL or its issue key: the orchestrator reading a document the task names, or the agent's knowledge_get tool — POST /api/run/knowledge/get. |
knowledge.run.search |
knowledge_session |
control-plane | stable | A run searches its project's knowledge with its knowledge session: the orchestrator assembling a task's reference material, or the agent's knowledge_search tool or cloop knowledge search in its sandbox — POST /api/run/knowledge/search. |
Payload keys:
knowledge.run.get—session_id,run_id,project,executor_id,task,purpose,lookup,documents,outcome,reason-
knowledge.run.search—session_id,run_id,project,executor_id,task,purpose,query,k,hits,documents,mode,outcome,reason -
knowledge.run.get—lookupsays how the document was named (id,urlorkey) with the value, scrubbed and cut at 512 bytes.documentsare the ids returned: none when the project's knowledge holds nothing by that name, which is also the answer for another project's document. knowledge.run.search— The entity id is the knowledge session's.queryis what the run searched for, scrubbed of anything shaped like a credential and cut at 512 bytes;documentsare the ids of the documents the answer held.purposeisinjectfor the orchestrator's own retrieval, whose documents the task cites as injected; otherwise they are recorded as the agent's reads.outcomeisok, orrefusedwithreasonfor a request the session's rate limit or a malformed query refused. A token that is not a live session's is refused before anything is read, and writes no row.
step.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
step.append |
step |
project | stable | One execution step finishes and its transcript is appended to the run. |
Payload keys, on every action above: step, task, exit_code, duration, time, input_tokens, output_tokens
state.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
state.save |
plan |
project | stable | The project's plan-level state is written: goal, run status, counters, and the mode flags in force. |
Payload keys, on every action above: goal, status, current_step, evolve_step, plan_version, task_count, total_input_tokens, total_output_tokens, auto_evolve, innovate_mode, parallel, max_parallel, pause_code, pause_detail, pause_resumes_at
state.save— Thepause_*keys appear only whenstatusispaused, and are what make an approval gate distinguishable from a spent budget or an exhausted usage window — before them all ~26 pause conditions recorded the same word.
config.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
config.set |
config |
project | stable | A configuration write lands, from the Settings panel, the CLI, or config validation repair. |
Payload keys, on every action above: yaml
config.set—yamlis the post-write document with secret-shaped values redacted, not a diff.
executor.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
executor.audience |
executor |
control-plane | stable | An admin admits a user or group to an executor, or withdraws one. |
executor.autoupdate |
executor |
control-plane | stable | An administrator changes whether the control plane upgrades executors unattended, which release they converge on, or how many move at once. |
executor.bind |
executor |
control-plane | stable | A project is pinned to one executor, at creation time or from the Executors panel. |
executor.cordon |
executor |
control-plane | stable | An executor is marked to refuse new work while finishing what it holds. |
executor.drain |
executor |
control-plane | stable | An executor is set to shed in-flight work as well as refuse new work. |
executor.enroll |
executor |
control-plane | stable | A remote agent completes outbound enrolment and joins the fleet. |
executor.failover |
executor_session |
control-plane | stable | A session is moved off an executor that stopped answering, or fails to be placed anywhere. |
executor.failover_exhausted |
executor_session |
control-plane | stable | An executor goes unreachable holding a session that was already re-dispatched as often as executors.failover.max_attempts allows, so nothing re-dispatches it. |
executor.firewall |
executor |
control-plane | stable | An admin sets or clears a device's firewall rule set, the superset every sandbox on it must fit inside. |
executor.isolation_verdict |
executor |
control-plane | stable | The isolation probe, run under an executor's runtime at reconcile, at a device's hello, after a runtime change, daily or on request, reaches a verdict that differs from the last one recorded for that executor and runtime. |
executor.labels |
executor |
control-plane | stable | An admin replaces an enrolled device's labels, the ones label grants are matched against. |
executor.limits |
executor |
control-plane | stable | An admin sets the most CPU, memory, disk and processes any one workload on an executor may be given. |
executor.revoke |
executor |
control-plane | stable | An enrolled agent's credential is revoked and it is removed from the fleet. |
executor.sandbox |
executor |
control-plane | stable | An admin sets where an executor's payloads run: the device's host, or a container on it. |
executor.state_change |
executor |
control-plane | stable | Liveness tracking moves an executor between health states without an operator asking. |
executor.unbind |
executor |
control-plane | stable | A project's executor pin is cleared and it falls back to registry placement. |
executor.uncordon |
executor |
control-plane | stable | A cordoned executor is returned to normal scheduling. |
executor.upgrade |
executor |
control-plane | stable | The control plane asks an enrolled device to replace its own binary with a published release and restart. |
executor.virtual |
executor |
control-plane | stable | An admin creates, edits or deletes a virtual executor on an enrolled device. |
Payload keys:
executor.audience—action,executor_id,principal_kind,principal_value,restricted,membersexecutor.autoupdate—action,enabled,target_version,max_in_flightexecutor.bind—action,executor_id,project,project_path,kind,viaexecutor.cordon—action,executor_id,reason,stateexecutor.drain—action,executor_id,reason,stateexecutor.enroll—action,executor_id,name,expires_at,workdir_root,labelsexecutor.failover—session_id,from,to,attempt,max_attempts,project_path,task_id,running_tasks,placed,acted_for,errorexecutor.failover_exhausted—session_id,from,attempt,max_attempts,project_path,running_tasks,nodes,errorexecutor.firewall—action,executor_id,from,to,fingerprint,cleared,constrainedexecutor.isolation_verdict—runtime,claim,verdict,status,previous_verdict,previous_status,point,detailexecutor.labels—action,executor_id,from,to,leases,sessions,taken_backexecutor.limits—action,executor_id,from,to,clearedexecutor.revoke—action,executor_id,name,kindexecutor.sandbox—action,executor_id,from,to,mode,clearedexecutor.state_change—from,to,reasonexecutor.unbind—action,project,project_pathexecutor.uncordon—action,executor_id,stateexecutor.upgrade—action,executor_id,requested_version,force,accepted,from_version,target_version,reason-
executor.virtual—action,executor_id,parent_id,name,from,to,reason -
executor.audience—actionis "admit" or "withdraw";restrictedis whether the executor is access-controlled after the change, andmembershow many principals remain. Withdrawing the last entry sets restricted=false, which widens the executor to the whole fleet — the one edit here that grants rather than revokes. executor.bind— Where a project's code runs is the most consequential setting on the hub, which is why the creation-dialog path emits this too.executor.enroll—actionrepeats the verb without the family prefix —enroll, notexecutor.enroll.executor.failover—placeddistinguishes a successful move from one with nowhere to go; thentois empty anderrorsays why. On a hub with single sign-onacted_fornames whom the replacement acts for — the person who started the run it continues, whose Claude account it runs on — and is absent when it acts for no one. A session past executors.failover.max_attempts is recorded as executor.failover_exhausted instead.executor.failover_exhausted—nodesnames every executor the run was lost on, oldest first, each with when it went unreachable. A workload that takes down every node it lands on ends here instead of on the next node.executor.firewall—fromandtodescribe the whole rule set — allowlist, denylist, ports, resolvers and the public-Internet switch — so a widened device is legible from this row alone.clearedis true when the rule set was removed, which leaves the device bounded only by its configuration.constrainedcounts the virtual executors and project rule sets the save narrowed to fit; each of those has its own row (executor.virtualwith action "constrain",project.firewallwith action "constrain").executor.isolation_verdict—claimis what the runtime's name claims (vm, userspace-kernel, host-kernel) andverdictwhat the probe found;statusis contradicted when the evidence shows less isolation than the claim, which refuses projects that require kernel isolation or virtualization.previous_*are empty for an executor's first verdict. A probe that could not run is recorded on the evidence row, not here.executor.labels—fromandtoare the device's labels before and after.leasesandsessionscount the live secret leases and egress sessions — the device's and its virtual executors' — that were re-judged by the new labels, andtaken_backhow many label grants they no longer matched; each of those has its own lease.revoke_sent or egress.close row naming the grant.executor.limits— Records its previous ceiling as well as the new one. A raised cap is the change worth reviewing, and it is invisible in the new value alone.clearedis true when the ceiling was removed, which makes the executor uncapped rather than capped at zero.executor.sandbox— One of two executor actions that record their previous value: it changes a containment boundary, sofromis what makes "when did this device stop isolating its workloads" answerable from the trail alone.clearedis true when the configuration was removed rather than replaced.executor.virtual—actionis "create", "update", "delete" or "constrain".fromandtodescribe the whole configuration — sandbox, firewall allow and deny lists, devices — so a widened firewall or an added device is visible from this row alone.reasonis set when the change was not asked for directly: a device's virtual executors are deleted with it when it is revoked, and narrowed ("constrain") when its firewall is tightened below what they allowed.
forwarder.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
forwarder.attach |
forwarder |
control-plane | beta | A run starts with a forwarded device: the hub relays the forwarder's stream to the executor that attaches it and gives it to the run's sandbox. |
forwarder.bind |
forwarder |
control-plane | beta | A forwarder's device is made available to a project's runs, optionally only on one virtual executor, by an operator or by approving a request. |
forwarder.bind_request |
forwarder |
control-plane | beta | Someone without executor.manage asks for a forwarder binding through the grant-request flow. |
forwarder.bind_request_approve |
forwarder |
control-plane | beta | An administrator approves a forwarder binding request, minting the binding. |
forwarder.bind_request_deny |
forwarder |
control-plane | beta | An administrator denies a forwarder binding request. |
forwarder.bind_request_expire |
forwarder |
control-plane | beta | A forwarder binding request lapses undecided. |
forwarder.bind_request_withdraw |
forwarder |
control-plane | beta | The requester withdraws a pending forwarder binding request. |
forwarder.detach |
forwarder |
control-plane | beta | A forwarded device is taken back from a run: the run ended, its binding or forwarder was revoked, or a side disconnected. |
forwarder.enroll |
forwarder |
control-plane | stable | An operator mints an enrollment token for a device forwarder. |
forwarder.expose |
forwarder |
control-plane | beta | An operator probes a forwarded device through the hub: a stream opened to the forwarder and, for USB, its device list read, handing the device to nothing. |
forwarder.revoke |
forwarder |
control-plane | stable | A device forwarder's enrollment token or long-lived credential is revoked. |
forwarder.unbind |
forwarder |
control-plane | beta | A forwarder binding is revoked by an operator, by the forwarder's own revocation, or by its TTL lapsing; a run holding the device has it detached. |
Payload keys:
forwarder.attach—action,forwarder_id,binding_id,project,executor_id,handle_id,run_idforwarder.bind—action,forwarder_id,binding_id,project,virtual_executor,expires_at,request_idforwarder.bind_request—action,forwarder_id,request_id,project,virtual_executor,ttl_secondsforwarder.bind_request_approve—action,forwarder_id,request_id,binding_id,expires_atforwarder.bind_request_deny—action,forwarder_id,request_id,noteforwarder.bind_request_expire—action,forwarder_id,request_idforwarder.bind_request_withdraw—action,forwarder_id,request_idforwarder.detach—action,forwarder_id,binding_id,project,executor_id,handle_id,reasonforwarder.enroll—action,forwarder_id,name,device,expires_atforwarder.expose—action,forwarder_id,device,reachable,memberforwarder.revoke—action,forwarder_id,kind-
forwarder.unbind—action,forwarder_id,binding_id,project,reason -
forwarder.bind— Gated on executor.manage, like enrollment. A run of the project gets the device in its sandbox while the binding is in force. forwarder.enroll— A forwarder bridges one on-prem USB or layer-2 device to the hub and runs no workload; it is not an executor.
executor_pool.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
executor_pool.audience |
executor_pool |
control-plane | beta | An administrator admits a principal to a pool's access list, or withdraws one. |
executor_pool.controller_status |
executor_pool |
control-plane | beta | A pool's controller reports that something keeps it from starting runners — a runner volume stuck Pending, a full one — or that it no longer does. |
executor_pool.cordon |
executor_pool |
control-plane | beta | An administrator cordons a pool — its runners are handed no queued run and its controller is minted no runner, while busy runners finish — or uncordons it. |
executor_pool.create |
executor_pool |
control-plane | beta | An administrator creates an executor pool. |
executor_pool.credential_mint |
executor_pool |
control-plane | beta | An administrator mints a controller credential for a pool. |
executor_pool.credential_revoke |
executor_pool |
control-plane | beta | An administrator revokes one of a pool's controller credentials. |
executor_pool.revoke |
executor_pool |
control-plane | beta | An administrator revokes a pool: its controller credentials, unredeemed runner tokens, live runners and queued runs go with it. |
executor_pool.run_queue |
executor_pool |
control-plane | beta | A project's run is queued for a pool, handed to a runner, or leaves the queue cancelled or failed. |
executor_pool.runner_enroll |
executor_pool |
control-plane | beta | A runner redeems its runner token and joins its pool as an ephemeral executor. |
executor_pool.runner_retire |
executor_pool |
control-plane | beta | A runner is retired: its one run settled, it left before it was given a run, or its pool was revoked. |
executor_pool.token_mint |
executor_pool |
control-plane | beta | A pool's controller is issued runner tokens: single-use, bound to the pool, valid for ten minutes at most. |
executor_pool.update |
executor_pool |
control-plane | beta | An administrator changes a pool's labels, capabilities, max_runners, owner or workdir root. |
Payload keys:
executor_pool.audience—pool_id,action,kind,value,restrictedexecutor_pool.controller_status—pool_id,healthy,reason,messageexecutor_pool.cordon—pool_id,name,cordoned,reasonexecutor_pool.create—pool_id,name,labels,capabilities,max_runners,owner,workdir_rootexecutor_pool.credential_mint—pool_id,credential_idexecutor_pool.credential_revoke—pool_id,credential_idexecutor_pool.revoke—pool_id,name,credentials,tokens,runners,cancelled_runsexecutor_pool.run_queue—pool_id,queue_id,action,project_path,starter,executor_id,reasonexecutor_pool.runner_enroll—pool_id,enrollment_id,executor_id,name,memberexecutor_pool.runner_retire—pool_id,executor_id,reason,queue_id,project_pathexecutor_pool.token_mint—pool_id,credential_id,count,enrollment_ids,holding,expires_at,runners,max_runners-
executor_pool.update—pool_id,name,changed,labels,capabilities,max_runners,owner,workdir_root -
executor_pool.controller_status— Recorded when the report changes, not every time the controller repeats it. The runs waiting in the pool's queue say the same while the report is under ten minutes old. executor_pool.cordon— Recorded only when the pool's state changes: cordoning a cordoned pool records nothing.executor_pool.credential_mint— The credential itself is shown once to the administrator and never recorded; the row names its public ID.executor_pool.revoke—runnerslists the executor IDs whose credentials were revoked; each is also recorded as executor_pool.runner_retire when its hub member ends its connection.executor_pool.run_queue—actionis queued, assigned, dispatched, cancelled or failed.executor_pool.token_mint— A refused request — the pool at max_runners, or revoked — is counted in cloop_executor_pool_mint_refusals_total rather than recorded here.
workspace.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
workspace.provision_end |
project |
control-plane | stable | Workspace provisioning finishes, successfully or not. |
workspace.provision_start |
project |
control-plane | stable | An executor begins materialising a project's source tree, before the harness starts. |
Payload keys:
workspace.provision_end—phase,project,executor_id,executor_kind,handle_id,kind,repo,ref,depth,grant_id,lease_id,duration_ms,error-
workspace.provision_start—phase,project,executor_id,executor_kind,handle_id,kind,repo,ref,depth,grant_id,lease_id -
workspace.provision_end— A row with noerroris the success case; there is no separate failure action. workspace.provision_start—depthis the shallow-fetch depth, or the stringfullwhen the spec asked for whole history.
sandbox.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
sandbox.image_denied |
project |
control-plane | stable | Image trust policy refuses a container image a project asked to run. |
Payload keys, on every action above: image, rule, reason, registry, repository, project
sandbox.image_denied— The one sandbox placement decision worth a permanent row: the others record a project asking for less than it could have, this one records it asking to execute code from somewhere the operator does not trust.
sandbox.attach.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
sandbox.attach.close |
sandbox_session |
control-plane | stable | An attached session ends, by the caller leaving or the sandbox going away. |
sandbox.attach.denied |
sandbox_session |
control-plane | stable | An attach request is refused, by permission, by scope, or because the target is not running. |
sandbox.attach.open |
sandbox_session |
control-plane | stable | A caller is admitted to a shell inside a running task's sandbox. |
Payload keys:
sandbox.attach.close—session_id,writable,executor_id,handle_id,task_id,command,detailsandbox.attach.denied—session_id,writable,executor_id,handle_id,task_id,command,detail-
sandbox.attach.open—session_id,writable,executor_id,handle_id,task_id,command -
sandbox.attach.open—writablerecords whether the second, narrowersandbox.attach.writepermission was exercised.
secret.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
secret.access_check |
secret |
control-plane | stable | A repository or cluster access decision is evaluated against a grant's constraints. |
secret.delete |
secret |
control-plane | stable | A secret is destroyed and every grant depending on it is revoked with it. |
secret.grant |
secret |
control-plane | stable | A grant is written authorising a subject to lease a secret. |
secret.lease |
secret |
control-plane | stable | A lease is issued — or refused — against the grants matching a request. |
secret.mint |
secret |
control-plane | stable | A credential is sealed and stored as a new secret. |
secret.release |
secret |
control-plane | stable | A workload finishes with a lease and it is dropped from the server-side record — or a recorded lease nobody holds any more is retired: its run ended while no hub process held it, or it lapsed after its holder stopped. |
secret.renew |
secret |
control-plane | stable | A live lease is renewed before it expires: re-issued to the same holder, extended in place while its run is live, or taken over by the hub process that adopted its run after the one holding it stopped. Also a credential renewed in place: a GitHub App installation token re-minted, or a Kubernetes share's ServiceAccount token replaced — by the hub at half its life, or by its owner from the terminal. |
secret.request |
secret |
control-plane | stable | A developer files a self-service request for access they do not have. |
secret.request_approve |
secret |
control-plane | stable | A reviewer approves a pending request and the grant it asked for is minted. |
secret.request_deny |
secret |
control-plane | stable | A reviewer refuses a pending request. |
secret.request_expire |
secret |
control-plane | stable | A pending request lapses with nobody having decided it. |
secret.request_withdraw |
secret |
control-plane | stable | A requester withdraws their own pending request. |
secret.revoke |
secret |
control-plane | stable | An operator marks a grant unusable. |
secret.unguarded_ack |
secret |
control-plane | stable | An administrator records that a kubeconfig grant whose namespaces or verbs only the Kubernetes access monitor enforces may be delivered on a hub that runs no monitor — or the attempt is refused. |
secret.unguarded_withdraw |
secret |
control-plane | stable | An unguarded acknowledgement on a kubeconfig grant is withdrawn, so the grant is refused again on a hub with no Kubernetes access monitor. |
Payload keys, on every action above: decision, subject, secret_id, secret_name, kind, grant_id, request_id, lease_id, executor_id, project_id, run_id, constraints, reason, task_id, host, port, bytes_up, bytes_down, expires_at
secret.grant— This is the row that answers "what authority exists"; the request family answers "who asked for it".secret.lease—decisionisallowordenyon every action in this family; a denial is a row, not a missing one.secret.release— An ordinary end of life. Compare the lease.revoke_* family, which is somebody deciding mid-run that a holder may no longer hold it.secret.renew— A Kubernetes share's renewal (Task 20416) is attributed tohubwhen the hub renewed it and to the owner when they pushed a token withcloop remote kube renew;reasonnames the ServiceAccount and both expiries,expires_atthe new one. A renewal that failed is adenyrow, written once per distinct cause.secret.request— The gap between this row and its approval is the number that says whether the request path is used or routed around.secret.request_expire— Its own action rather than a flavour of deny: denied is an answer, expired is the absence of one, and conflating them hides a queue nobody is working.secret.unguarded_ack— Without it such a grant is refused at every lease (Task 20420).reasonnames what goes unenforced and the administrator's own justification;grant_idthe grant, which carries the acknowledgement until it is withdrawn.
secret.lease.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
secret.lease.sweep |
secret |
control-plane | stable | The periodic sweeper reaps leases whose TTL has passed. |
Payload keys, on every action above: decision, wiped, vanished, skipped, failed, expired
secret.lease.sweep— One row per sweep, not per lease — the counts are the point.
lease.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
lease.refresh |
lease |
control-plane | stable | A GitHub App token re-minted before its hour ran out is delivered to an executor holding the lease's token file, or cannot be. |
lease.revoke_acked |
lease |
control-plane | stable | An executor confirms it destroyed the material for a revoked lease. |
lease.revoke_failed |
lease |
control-plane | stable | A revocation fails to land on an executor that still holds the credential. |
lease.revoke_sent |
lease |
control-plane | stable | A revocation is queued for delivery to the executors holding a lease. |
Payload keys:
lease.refresh—decision,lease_id,grant_id,executor_id,project_id,files,handles,eventual,error,reasonlease.revoke_acked—decision,lease_id,grant_id,executor_id,project_id,action,state,env_scrubbed,files_removed,reasonlease.revoke_failed—decision,lease_id,grant_id,executor_id,project_id,action,state,error,reason-
lease.revoke_sent—decision,lease_id,grant_id,executor_id,project_id,action,secrets,holders,reason -
lease.refresh— The re-mint itself is a secret.renew row; this one says whether the workload got the new token. A deny means it did not — an agent too old for the refresh frame, a device offline, a Role without patch on secrets — and the run's GitHub access ends when the token it holds expires. lease.revoke_failed— The row an incident response cares about: the credential is still out there.
github_app.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
github_app.token_destroy |
secret |
control-plane | stable | A GitHub App installation token is deleted at GitHub. |
Payload keys, on every action above: decision, subject, secret_id, secret_name, kind, grant_id, request_id, lease_id, executor_id, project_id, run_id, constraints, reason, task_id, host, port, bytes_up, bytes_down, expires_at
github_app.token_destroy— Deliberately not secret.revoke: a github_app token is destroyed on every ordinary lease release, so folding these in would make "how many grants did an operator withdraw" a number dominated by routine teardown.
egress.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
egress.close |
secret |
control-plane | stable | An egress proxy session closes, carrying the bytes it moved in each direction. |
egress.connect |
secret |
control-plane | stable | A sandbox's connection attempt is evaluated against the session's host allowlist. |
egress.grant |
secret |
control-plane | stable | A subject is authorised to reach the network through the hub's egress proxy. |
egress.redeem |
secret |
control-plane | stable | A proxy session is minted against a matching egress grant. |
egress.renew |
secret |
control-plane | stable | A hub renews a live proxy session for a run that is still going, after re-reading its grant. |
egress.request |
secret |
control-plane | stable | An HTTP request passes through the egress proxy. |
egress.restore |
secret |
control-plane | stable | The hub process that adopted a run after the one serving it stopped restores the run's proxy session, after re-reading its grant. |
egress.revoke |
secret |
control-plane | stable | An egress authorisation is marked unusable. |
Payload keys, on every action above: decision, subject, secret_id, secret_name, kind, grant_id, request_id, lease_id, executor_id, project_id, run_id, constraints, reason, task_id, host, port, bytes_up, bytes_down, expires_at
egress.connect—hostandportname the attempted destination;decisionsays whether it was reached.egress.renew— Allowed rows carry the newexpires_at. A refused renewal is adenyrow; the session it concerned then closes with anegress.closerow saying why.egress.restore— Allowed rows carry the counters the session resumes from (bytes_up,bytes_down), which keep counting against the grant's quota, and the reason names the process that held it. Adenyrow is a session that will not come back: its grant was revoked or expired, or it lapsed, while no hub process held it (Task 20383).
gitproxy.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
gitproxy.fetch |
gitproxy |
control-plane | stable | A read passes through the proxy. |
gitproxy.push_allowed |
gitproxy |
control-plane | stable | A push is checked against the session's branch allowlist, scanned for the credentials its run was given (unless executors.git_proxy.push_scan is off), and forwarded. |
gitproxy.push_denied |
gitproxy |
control-plane | stable | A push is refused because it names a ref the session may not write — or because its pack could not be scanned for the run's credentials: past a push_scan limit, or unreadable. |
gitproxy.push_secret_refused |
gitproxy |
control-plane | stable | A push the branch policy allowed is refused because it carries a credential the session's run was given: an env value, a kubeconfig token or client key, registry auth, an egress proxy credential, a GitHub token — as delivered, base64-encoded or percent-encoded — found in a commit, tree, blob, tag, delta insert, ref name or push option. |
gitproxy.rejected |
gitproxy |
control-plane | stable | A request is refused before any policy could be evaluated — no session, bad credential, unknown repository. |
gitproxy.session_closed |
gitproxy |
control-plane | stable | A proxy session ends and its credential stops working. |
gitproxy.session_minted |
gitproxy |
control-plane | stable | A proxy session is created for a task, scoping which repository and refs it may touch. |
gitproxy.session_renewed |
gitproxy |
control-plane | stable | A workspace's pinned proxy session nears its end while its run is still alive, and the hub renews it for another session_minutes. |
gitproxy.session_restored |
gitproxy |
control-plane | stable | The hub process that adopted a run restores the run's proxy session under its original id and token, with an upstream credential re-derived from the run's lease. |
Payload keys:
gitproxy.fetch—kind,session_id,repo,project_id,task_id,refs,detailgitproxy.push_allowed—kind,session_id,repo,project_id,task_id,refs,detailgitproxy.push_denied—kind,session_id,repo,project_id,task_id,refs,detail,scan,run_id,findingsgitproxy.push_secret_refused—kind,session_id,repo,project_id,task_id,refs,detail,scan,run_id,findingsgitproxy.rejected—kind,session_id,repo,project_id,task_id,refs,detailgitproxy.session_closed—kind,session_id,repo,project_id,task_id,refs,detailgitproxy.session_minted—kind,session_id,repo,project_id,task_id,refs,detailgitproxy.session_renewed—kind,session_id,repo,project_id,task_id,refs,detail-
gitproxy.session_restored—kind,session_id,repo,project_id,task_id,refs,detail -
gitproxy.push_denied— The row that matters in this family: it is the only place a sandbox's attempt to write outside its lane is recorded.refsnames what it tried to push. A push the scan could not finish carriesscan(limitorunreadable) andrun_id, anddetailnames the limit and its setting (Task 20436). gitproxy.push_secret_refused—findingsname each object, its path and commit where the pack says, and the grant and secret the value came from; nothing records the value. The forge got no complete pack and no ref moved. A push refused because it could not be scanned — past apush_scan_*limit, or a pack the proxy cannot read — is agitproxy.push_deniedrow withscanset (Task 20436).gitproxy.session_closed— Also written for a session that ended while no hub process served it — not restored with its run, or retired by the leader's janitor once it lapsed — with the reason indetail.gitproxy.session_renewed—detailgives the new deadline. Renewed only while the workspace lease it stands on is kept alive and the run's executor still holds the workload; the session is closed once the run is done with it (Task 20422).gitproxy.session_restored—detailnames the hub process that held the session until then. A session that could not be restored gets agitproxy.session_closedrow instead (Task 20383).
kubeguard.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
kubeguard.rejected |
kubeguard |
control-plane | stable | A request is refused before its session could be identified. |
kubeguard.request_allowed |
kubeguard |
control-plane | stable | A Kubernetes request is admitted by policy. |
kubeguard.request_denied |
kubeguard |
control-plane | stable | A Kubernetes request is refused by the session's verb and resource policy, or because it would hand the workload a credential of its own. |
kubeguard.session_closed |
kubeguard |
control-plane | stable | A Kubernetes proxy session ends. |
kubeguard.session_minted |
kubeguard |
control-plane | stable | A Kubernetes proxy session is created for a task against one cluster and context. |
kubeguard.session_restored |
kubeguard |
control-plane | stable | The hub process that adopted a run restores the run's Kubernetes proxy session under its original id and token, with the cluster credential re-derived from the run's lease and its policy narrowed to what the grant and the hub allow now. |
Payload keys, on every action above: kind, session_id, cluster, context, verb, resource, namespace, object, reason, project_id, task_id, executor_id, grant_id, lease_id, detail
kubeguard.request_allowed— Sampled rather than exhaustive — a watch against a busy cluster would otherwise be the whole table.kubeguard.request_denied— This is what enforces a read-only grant on a cluster from outside the sandbox:verb,resourceandnamespacesay exactly what was attempted.reasonclassifies it;service_account_token_secret,workload_service_account,host_access,rbac_binding,rbac_role,certificate_signing_requestandunreadable_bodyare a write grant's attempts to obtain a credential that would bypass the monitor (Task 20420).kubeguard.session_closed— Also written for a session that ended while no hub process served it, with the reason indetail.kubeguard.session_restored—detailnames the hub process that held the session until then (Task 20383).
ci.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.rejected |
ci_session |
control-plane | beta | A relay request is refused before a session could be established. |
Payload keys, on every action above: kind, session_id, rule_id, rule_name, project, subject, repository, ref, workflow, actor, run_id, method, path, model, status, reason, input_tokens, output_tokens, detail, at
ci.session.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.session.closed |
ci_session |
control-plane | beta | A relay session ends, by expiry or by revocation. |
ci.session.minted |
ci_session |
control-plane | beta | A pipeline's OIDC token matches an allowlist rule and a relay session is issued. |
ci.session.restored |
ci_session |
control-plane | beta | A hub process receives a call presenting the token of a recorded relay session whose holder stopped, takes the record over, holds the session to its rule as the rule stands now, and serves it under its original id and token. |
ci.session.revoked |
ci_rule |
control-plane | beta | An operator revokes a live relay session from the CI panel. |
ci.session.suspended |
ci_session |
control-plane | beta | A hub process stops serving a recorded relay session without ending it here: it is stopping gracefully, and the session's spend is written and its record stays open; or the record was closed or taken over by another hub process, whose row says why. |
Payload keys:
ci.session.closed—kind,session_id,rule_id,rule_name,project,subject,repository,ref,workflow,actor,run_id,method,path,model,status,reason,input_tokens,output_tokens,detail,atci.session.minted—kind,session_id,rule_id,rule_name,project,subject,repository,ref,workflow,actor,run_id,method,path,model,status,reason,input_tokens,output_tokens,detail,atci.session.restored—kind,session_id,rule_id,rule_name,project,subject,repository,ref,workflow,actor,run_id,method,path,model,status,reason,input_tokens,output_tokens,detail,atci.session.revoked—rule_id,rule_name,repository,ref,condition,models,detail,error-
ci.session.suspended—kind,session_id,rule_id,rule_name,project,subject,repository,ref,workflow,actor,run_id,method,path,model,status,reason,input_tokens,output_tokens,detail,at -
ci.session.restored—detailnames the hub process that held it until then and anything its rule narrowed. A session whose rule was deleted, disabled or no longer admits the pipeline gets aci.session.closedrow instead, and its call a 401 (Task 20390). ci.session.revoked— Also written for a suspended session — one no hub process serves — whose record the revocation ends, so its job's next call is refused rather than restored (Task 20390).ci.session.suspended— After a graceful stop the session is restored by the hub process that receives its job's next call (Task 20390); a session with no record is closed instead, with aci.session.closedrow. One whose record another process closed or took over is ended, or served, there.
ci.exchange.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.exchange.accepted |
ci_session |
control-plane | beta | A pipeline's OIDC token is verified and exchanged for relay credentials. |
ci.exchange.rejected |
ci_session |
control-plane | beta | A token exchange fails: bad signature, wrong issuer, or no rule admits the claims. |
Payload keys, on every action above: kind, session_id, rule_id, rule_name, project, subject, repository, ref, workflow, actor, run_id, method, path, model, status, reason, input_tokens, output_tokens, detail, at
ci.exchange.rejected— The row to alert on. A pipeline that should be admitted and is not looks identical here to one that should never have asked.
ci.relay.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.relay.allowed |
ci_session |
control-plane | beta | A relayed API call is forwarded upstream on a live session. |
ci.relay.denied |
ci_session |
control-plane | beta | A relayed API call is refused — expired session, unsupported path, or a quota. |
Payload keys, on every action above: kind, session_id, rule_id, rule_name, project, subject, repository, ref, workflow, actor, run_id, method, path, model, status, reason, input_tokens, output_tokens, detail, at
ci.rule.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.rule.created |
ci_rule |
control-plane | beta | An allowlist rule is added, widening which pipelines may authenticate. |
ci.rule.deleted |
ci_rule |
control-plane | beta | An allowlist rule is removed. |
ci.rule.updated |
ci_rule |
control-plane | beta | An allowlist rule is edited; live sessions it no longer admits are revoked in the same operation. |
Payload keys, on every action above: rule_id, rule_name, repository, ref, condition, models, detail, error
ci.rule.created—matchis the CEL expression the rule admits on. Rules are the CI surface's whole access-control policy, so these three rows are its change log.
ci.config.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
ci.config.updated |
ci_rule |
control-plane | beta | The CI relay's own settings change — whether it is enabled, its issuer, its session ceiling. |
Payload keys, on every action above: rule_id, rule_name, repository, ref, condition, models, detail, error
model_relay.session.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
model_relay.session.closed |
model_relay_session |
control-plane | beta | A run's relay session ends: its run ended or did not start, its lease was released, revoked or lapsed, its grant was revoked, or it expired. |
model_relay.session.minted |
model_relay_session |
control-plane | beta | A run on an isolating executor is dispatched with a model relay session in place of its Claude credential. |
model_relay.session.restored |
model_relay_session |
control-plane | beta | The hub process adopting a run takes its relay session over with the run's lease, re-deriving the credential from the grant and holding the session to today's model allowlist. |
model_relay.session.suspended |
model_relay_session |
control-plane | beta | A hub process stops serving a recorded relay session without ending it: it is stopping gracefully, or another process took the session's record over with the run's lease. |
Payload keys, on every action above: kind, session_id, project, run_id, lease_id, grant_id, executor_id, payer, upstream_kind, method, path, model, status, reason, input_tokens, output_tokens, detail, at
model_relay.session.closed—input_tokensandoutput_tokensare what the session spent in all, as the API counted it;input_tokensincludes cache reads and writes.
model_relay.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
model_relay.denied |
model_relay_session |
control-plane | beta | A relayed model call is refused before it reaches the API: its model is not allowed, the identity paying for the run has spent a daily budget, or the request is not one a run may make. |
model_relay.models_set |
project |
control-plane | beta | A project's maintainer narrows, or clears, the models its relayed runs may use. |
model_relay.not_relayed |
project |
control-plane | beta | The model relay is on, and a run on an isolating executor is dispatched with its Claude credential delivered to the sandbox as before. |
model_relay.rejected |
model_relay_session |
control-plane | beta | A model call presents the token of a relay session that has ended, and is refused with the reason it ended. |
model_relay.usage_gap |
project |
control-plane | beta | A relayed run ends and the usage it reported to its project's ledger differs from what the relay counted from the API's own responses. |
Payload keys:
model_relay.denied—kind,session_id,project,run_id,lease_id,grant_id,executor_id,payer,upstream_kind,method,path,model,status,reason,input_tokens,output_tokens,detail,atmodel_relay.models_set—project,modelsmodel_relay.not_relayed—project,run_id,executor_id,credential_kind,reasonmodel_relay.rejected—kind,session_id,project,run_id,lease_id,grant_id,executor_id,payer,upstream_kind,method,path,model,status,reason,input_tokens,output_tokens,detail,at-
model_relay.usage_gap—project,run_id,session_ids,payer,relay_tokens,relay_usd,reported_tokens,reported_usd,gap_tokens,gap_usd -
model_relay.denied—reasonis the relay's refusal code:budget_exhausted,model_not_allowed,path_not_allowed,method_not_allowed,body_too_largeorbody_malformed. model_relay.models_set— An emptymodelsclears the project's list, and the hub's allowlist applies to its runs again.model_relay.not_relayed—reasonsays why: the credential's kind has not been proven through the relay against the real API, or the device's agent predates handing the hub's CA to the harness.model_relay.rejected— A call with no usable token at all is counted incloop_model_relay_requests_totaland not written here.model_relay.usage_gap— The relay's count is what the payer was charged; the run's ledger gains a row bringing its total to it. A claudecode run reports no tokens at all, so its whole relayed spend shows as the gap.
authz.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
authz.denied |
permission |
either | stable | Any permission check refuses a caller. |
authz.granted |
permission |
either | stable | A privileged permission is exercised successfully. |
Payload keys, on every action above: outcome, permission, role, source, scope, subject, method, path, binding
authz.denied— Every denial, unlike the grant side.scopesays whether the check was fleet-wide or against one project;bindingnames the runtime rule when one decided it. Neither action in this family is emitted when RBAC is not in force for the caller — with no identity provider configured every request is granted everything and there is no decision to record, so an absence of rows here means "nobody was refused" only on a hub that has SSO or API tokens. Project-scoped decisions land in that project's trail rather than the control plane's.authz.granted— Not every allow: ordinary reads would drown the table, so only privileged permissions are recorded on this side. Home varies with scope — a check against a project lands in that project's chain, a fleet-wide one in the hub's — so answering "what was this subject allowed to do" needs both.
api_token.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
api_token.approve_denied |
api_token |
control-plane | stable | An approval is refused: no role policy is in force, or the approver does not hold the token's roles. |
api_token.approved |
api_token |
control-plane | stable | An administrator approves a token minted with no role policy in force, which a hub enforcing one refuses until then (Task 20422). |
api_token.auth_failed |
api_token |
control-plane | stable | A request presents a token that does not authenticate: unknown, revoked, or expired. |
api_token.create_denied |
api_token |
control-plane | stable | A token mint is refused because it would grant more than the caller holds. |
api_token.created |
api_token |
control-plane | stable | A scoped API token or a display-glasses link is minted, from the UI or the CLI. |
api_token.revoked |
api_token |
control-plane | stable | A token or glasses link is revoked, or rotated — a rotation revokes the old one. |
Payload keys:
api_token.approve_denied—reason,roles,project_scopeapi_token.approved—name,roles,project_scope,minted_under,created_by,created_at,viaapi_token.auth_failed—reason,ip,method,pathapi_token.create_denied—reason,roles,project_scopeapi_token.created—name,roles,project_scope,expires_at,kind,owner,minted_under-
api_token.revoked—name,roles,reason -
api_token.approved— From the dashboard the approver must hold every role the token carries, under the policy now in force;via: cliis an operator at the control plane. api_token.auth_failed— Filed against the token's public id (cloop_pat_<id>), never the secret half, and against(unparseable)when the credential is not even shaped like a token — so a garbage value is not echoed back into the trail. Rate of this action byipis the credential-stuffing signal.awaiting_approvalis a token minted with no role policy in force, refused on a hub that enforces one until an administrator approves it.api_token.create_denied— The anti-escalation check. A maintainer trying to mint an admin token lands here.api_token.created—kinddistinguishes a service-account token from a glasses link, which is a token with one narrow role and a URL as its only delivery mechanism.minted_underispolicywhen the hub's role policy was in force,no_policywhen it was not,operatorfrom the CLI (Task 20422).
static_token.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
static_token.retired |
static_token |
control-plane | stable | The static admin token is retired, from the Settings card or cloop hub token static retire. |
Payload keys, on every action above: fingerprint, reason, via, forced, self, os_user
static_token.retired— Filed against the token's fingerprint — a SHA-256 of the value, never the value.selfis true when the static token retired itself;forcedwhen the retirement overrode the refusal to leave a hub without single sign-on or an admin API token with no way in. Recorded in the same commit as the retirement, so a retirement with no row cannot happen.
session.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
session.claims_rejected |
session |
control-plane | stable | The IdP answers the re-assertion by refusing the claims outright. |
session.claims_stale |
session |
control-plane | stable | A privileged operation is blocked because the session's claims are older than the freshness bound allows. |
session.claims_unverified |
session |
control-plane | stable | The hub cannot reach the IdP to re-assert a session's claims. |
session.created |
session |
control-plane | stable | A sign-in completes and a durable session is written. |
session.expired |
session |
control-plane | stable | The session janitor removes a session past its absolute or idle deadline. |
session.idp_revoked |
session |
control-plane | stable | The identity provider reports the authorisation behind a session is gone. |
session.reauth |
session |
control-plane | beta | A privileged request reaches the hub on a session whose last interactive sign-in is older than ui.oidc.sudo_max_age_minutes (sudo mode), or the confirm-it's-you sign-in that answers one completes. |
session.renewal_mismatch |
session |
control-plane | stable | A silent renewal from the user's browser is answered by the identity provider for a different subject than the session belongs to. |
session.revoked |
session |
control-plane | stable | A user logs out, or an operator terminates a session from the UI or the CLI. |
session.role_narrowed |
session |
control-plane | stable | A re-assertion finds the session's claims now map to a lower role than it held. |
Payload keys:
session.claims_rejected—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.claims_stale—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.claims_unverified—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.created—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.expired—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.idp_revoked—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.reauth—session_id,subject,email,ip,user_agent,kind,client,outcome,reason,max_age_seconds,authenticated_at,method,path,replaces,suppressedsession.renewal_mismatch—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,clientsession.revoked—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,client-
session.role_narrowed—event,session_id,subject,email,actor,reason,ip,user_agent,prior_role,role,dropped_claims,issued_at,selector,selected,via,os_user,kind,client -
session.claims_stale— A block, not a revocation: the session stays, and ordinary reads keep working. session.reauth—outcomeisstale-refusedfor the refused request —methodandpathname it,authenticated_atis the session's last interactive sign-in (absent: none on record) andmax_age_secondsthe window it missed; at most thirty such rows a minute are written, the next one carryingsuppressed, whilecloop_session_reauth_totalcounts every one.grantedis a sign-in the hub started withprompt=login&max_age=0that the provider answered for the same person:session_idis the fresh session it created andreplacesthe one that asked.refusedis one that did not count —reasonsubject_mismatch(another account signed in),not_reauthenticated(the provider'sauth_timeis older than the request, so it did not ask), oridp_error: <code>; nothing is created then. API tokens and the static token are not sessions and never produce this row.session.renewal_mismatch— The session is left exactly as it was: nothing of the other identity is applied or recorded. The benign reading is a user who switched accounts at the provider in another tab; the other is a browser signed into somebody else's account at the provider in the hope that the renewal adopts it.ipanduser_agentare the browser that performed the renewal.session.role_narrowed—prior_roleandrolebracket the demotion;dropped_claimsnames what the IdP stopped asserting.viaisbrowser_renewalwhen the narrowed claims arrived through a silent renewal from the user's browser, and absent when the hub's own refresh found them.
device.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
device.approved |
device_authorization |
control-plane | beta | A signed-in browser approves a terminal's code at /device and completes the identity provider round trip that writes the CLI session. |
device.code_issued |
device_authorization |
control-plane | beta | A terminal asks the hub for a sign-in code (cloop login): POST /api/device/code. |
device.denied |
device_authorization |
control-plane | beta | A terminal's code is denied at /device, or the hub refuses the approval on the approver's behalf. |
Payload keys, on every action above: device_id, client_ip, client_user_agent, client_hostname, expires_at, session_id, subject, email, ip, user_agent, reason, reauth, kind, client
device.approved—session_idis the CLI session's id, whose ownsession.createdrow (kindcli,viadevice_grant) follows;ipanduser_agentare the approving browser's, theclient_*keys the terminal's.reauthis true when the browser session was older thanui.oidc.device_approval_max_age_minutesand the provider was asked for a fresh sign-in.device.code_issued— Unauthenticated by design — the terminal has no credential yet — and rate-limited per address.device_idis a prefix of the device code's digest, never the code;client_hostnameis what the terminal reported and proves nothing.device.denied—reasonisdenied_at_approval_pagefor a person pressing Deny;identity_refusedwhen a runtime deny binding (an offboarding) matched the approving identity;session_errorwhen the session could not be written. The actor ishubfor the last two.
request.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
request.host_refused |
request |
control-plane | beta | A hub without sign-in refuses a request addressed to a host name it does not answer to — the shape DNS rebinding takes. |
request.origin_refused |
request |
control-plane | beta | The hub refuses a state-changing request or a WebSocket handshake because a page on another origin made it, or because its body is form data or text/plain. |
Payload keys, on every action above: reason, method, path, origin, sec_fetch_site, host, content_type, ip, user_agent, session, open_hub, own_origin, suppressed
request.host_refused— Only a hub with neither SSO nor a static token checks the Host: rebinding cannot carry a credential to a hub that has one. A burst after a new DNS name was pointed at the hub is that name missing fromui.allowed_hosts. Rate-limited likerequest.origin_refused.request.origin_refused— The browser that sent it belongs to whoever was using it: the row records the page that asked (origin,sec_fetch_site), not an identity, andsessionsays only whether that browser carried a sign-in cookie.reasonis acloop_cross_origin_refusals_totallabel. Rows are written at most 30 a minute;suppressedcounts the refusals since the previous row that were not.
role_binding.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
role_binding.deleted |
role_binding |
control-plane | stable | A runtime binding is removed and the identity falls back to its configured role. |
role_binding.denied |
role_binding |
control-plane | stable | A runtime deny binding is written, demoting an identity ahead of the IdP catching up. |
role_binding.granted |
role_binding |
control-plane | stable | A runtime binding is written mapping a claim to a role, without an IdP or config change. |
Payload keys, on every action above: id, effect, claim, value, role, project, executor, binding_reason, created_at, created_by, reason, via, os_user
role_binding.denied— The incident-response verb: it takes authority away now, from a CLI, without a browser.
quota.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
quota.denied |
quota |
control-plane | stable | Admission control refuses an operation because the caller is at a resource ceiling. |
quota.override_cleared |
quota |
control-plane | stable | A per-identity override is removed and the identity returns to the default ceiling. |
quota.override_set |
quota |
control-plane | stable | A per-identity quota override is written, from the UI or the CLI. |
quota.spend_refused |
project |
control-plane | stable | A run is stopped between tasks because the identity paying for it is over its spend limit. |
Payload keys:
quota.denied—limit,used,requested,source,transient,method,pathquota.override_cleared—target,identity,cleared_limits,reason,via,os_userquota.override_set—target,identity,limits,unset,reason,via,os_user-
quota.spend_refused—identity,resource,limit,used,source,project,outcome -
quota.denied— The identity is the row's actor and the resource is its entity id, so neither repeats in the payload. quota.override_set— Two emitters with different shapes: the UI writestargetplus one key per resource, the CLI writesidentitywith the limits nested underlimits.quota.spend_refused— Enforced between tasks, not mid-task: a task already running is allowed to finish.
resource_ceiling.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
resource_ceiling.cleared |
project_resource_limit |
control-plane | stable | A per-project ceiling is removed and the project returns to the fleet ceiling alone. |
resource_ceiling.set |
project_resource_limit |
control-plane | stable | A per-project resource ceiling is written with cloop hub limits set. |
Payload keys:
resource_ceiling.cleared—target,project,reason,via,os_user-
resource_ceiling.set—target,project,cpu_millis,memory_mb,disk_mb,pids,reason,via,os_user -
resource_ceiling.set— The values are the merged ceiling that was stored, not the flags that were passed:setmerges onto the existing row, so the flags alone would not say what the project ended up capped at.
sealing_key.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
sealing_key.escrowed |
sealing_key |
control-plane | beta | cloop hub key escrow export wraps CLOOP_SECRET_KEY under an operator's passphrase, or cloop hub key escrow verify --record records a verified escrow again after a restore. |
sealing_key.kms_rewrapped |
sealing_key |
control-plane | beta | A key-encryption key held by a KMS is wrapped again under the key the KMS now encrypts under — by the hub's cluster leader when the KMS reports a rotation of its key, or by cloop hub key rewrap. Nothing sealed under the key changes. |
sealing_key.retired |
sealing_key |
control-plane | stable | A superseded sealing key is retired once nothing is wrapped under it. |
sealing_key.root_changed |
sealing_key |
control-plane | stable | cloop hub key rotate --retire-previous finishes a change of root — of CLOOP_SECRET_KEY, onto a KMS or off one: every hub member held the new root (and, for a passphrase root, an escrow of it was on record), every sealed row was rewrapped onto a key of it, and every key of the previous root was retired. Written when the run ends, finished or not. |
sealing_key.rotated |
sealing_key |
control-plane | stable | The secret store's sealing key is rotated and stored payloads are re-wrapped under the new one. |
Payload keys:
sealing_key.escrowed—escrow_id,key_fingerprint,kdf,created_at,recorded,viasealing_key.kms_rewrapped—key_id,kms,from_kms_key,to_kms_key,viasealing_key.retired—key_id,viasealing_key.root_changed—root,previous_root,minted,to_key,rewrapped,skipped,failed,retired,remaining,members,escrow_id,complete,via-
sealing_key.rotated—from_primary,to_key,rewrapped,skipped,failed,complete,via -
sealing_key.escrowed— Carries the key's fingerprint, never the key or the passphrase.cloop hub doctorcompares the escrow on record against the key in use and fails when they differ. sealing_key.kms_rewrapped— Carries KMS key ids, never a ciphertext. Once no live key names the KMS's old key version incloop hub key list, that version holds nothing of the hub's and can be disabled in the KMS.sealing_key.root_changed— Roots are named by fingerprint, the name every checkpoint and escrow carries, never by value, and a KMS root by its name (Task 20433), so moving onto a KMS or off one is recorded here too. The rotation and each retirement are recorded as sealing_key.rotated and sealing_key.retired as well.sealing_key.rotated— Envelope encryption means rotation re-wraps data keys rather than re-encrypting payloads, so this row is cheap even on a large store.
hub_ca.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
hub_ca.emergency_rotated |
hub_ca |
control-plane | beta | cloop hub ca rotate --now replaces every hub CA at once with a fresh one, for a key believed compromised. |
hub_ca.leaf_issued |
hub_ca |
control-plane | beta | A hub process issues a credential monitor's serving certificate from the hub CA: at its first start, when the cluster leader renews one, when a member needs a name the stored one lacks, or when a rotation activated another CA. |
hub_ca.minted |
hub_ca |
control-plane | beta | A hub process mints the control plane's certificate authority, the first time any starts a credential monitor under tls: auto. |
hub_ca.previous_retired |
hub_ca |
control-plane | beta | cloop hub ca rotate --retire-previous deletes the replaced CA with secure_delete on, so its sealed key is gone from the database file, and drops it from every bundle. |
hub_ca.rotation_abandoned |
hub_ca |
control-plane | beta | cloop hub ca rotate --abort destroys a staged CA that was never activated, or an emergency rotation abandons one. |
hub_ca.rotation_activated |
hub_ca |
control-plane | beta | cloop hub ca rotate --activate makes the staged CA the one leaves are issued from; the cluster leader then reissues every leaf. |
hub_ca.rotation_staged |
hub_ca |
control-plane | beta | cloop hub ca rotate --stage (or Settings → Hub health) mints a CA to replace the hub's, sealed under the KEK registry like the first (Task 20438). |
hub_ca.sessions_ended |
hub_ca |
control-plane | beta | A hub member ends the git proxy, Kubernetes monitor and model relay sessions of the runs an emergency rotation named, within a minute of it. |
Payload keys:
hub_ca.emergency_rotated—rotation,replaced,active,names,cut_runs,viahub_ca.leaf_issued—monitor,serial,names,not_after,replaced,issuer,reasonhub_ca.minted—serial,not_after,fingerprinthub_ca.previous_retired—rotation,retired,active,cut_runs,emergency,viahub_ca.rotation_abandoned—rotation,abandoned,active,detail,emergency,viahub_ca.rotation_activated—rotation,from,to,forced,emergency,viahub_ca.rotation_staged—rotation,from,to,fingerprint,not_after,names,emergency,via-
hub_ca.sessions_ended—rotation,member,runs,git_proxy,kube_guard,model_relay -
hub_ca.emergency_rotated— Alert on it.cut_runsare the runs that trusted only the replaced CAs; each hub member ends their proxy sessions and recordshub_ca.sessions_ended. hub_ca.leaf_issued—reasonis first, renewal, names, activation or unreadable;issueris the serial of the CA that signed it.hub_ca.minted— Every sandbox is told to trust this CA for the monitors' URLs, so a secondhub_ca.mintedrow for one control plane — itshub_tlstable lost or restored without it — means sandboxes holding the previous CA will refuse the next certificate the monitors serve.hub_ca.previous_retired— A backup taken before this row still holds the retired CA's sealed key.hub_ca.rotation_activated—forcednames the runs dispatched before the stage that an operator activated past with --force: they stop verifying the credential monitors.hub_ca.rotation_staged— From this row on every dispatch trusts both CAs; leaves still come fromfrom.namesare the new CA's X.509 name constraints, empty when it was minted without them.hub_ca.sessions_ended— Counts per monitor are what this member held; a run's sessions on another member are in that member's row.
oidc.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
oidc.config.updated |
config |
control-plane | stable | The hub's single sign-on configuration is changed, from the Settings panel or cloop config set ui.oidc.*. |
oidc.rbac.enforced |
config |
control-plane | stable | An admin enforces deny-by-default from Settings → Single sign-on on a hub whose single sign-on ran without a role policy: default_role: none, plus a hub-wide admin mapping for them unless they already hold admin. |
Payload keys:
oidc.config.updated—changed,enabled,was_enabled,issuer,default_role,admin_emails,role_mappings,require_idp,require_rbac,rbac_enforced,was_rbac_enforced,restart_required-
oidc.rbac.enforced—issuer,default_role,bound,admin_emails,role_mappings,restart_required -
oidc.config.updated—changedlists the field names that moved, so the row answers what was touched without storing two copies of the block. The client secret appears inchangedwhen it moves and nowhere else — not its value, not its length.admin_emailsandrole_mappingsare counts, not contents.rbac_enforcedandwas_rbac_enforcedsay whether the saved block puts a role policy in force after and before the change, so the rows where RBAC switched on or off are a filter, not a reconstruction; the Settings panel makes either switch only when the request confirms it.restart_requiredrecords whether the change was live yet, because the authenticator is built at startup: a row with it set means the hub was still running the previous configuration when the change landed. oidc.rbac.enforced— It changes ui.oidc asoidc.config.updateddoes, and is written instead of it, so a query for who last changed single sign-on reads both.boundis the mapping added for the acting admin assub=<subject>— the subject, because an email can be renamed at some providers — and empty when they were already an admin.admin_emailsandrole_mappingsare counts. Not live until the hub restarts, whichrestart_requiredrecords.
telemetry.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
telemetry.config.updated |
config |
control-plane | stable | Front-end telemetry collection is switched on or off, or narrowed to particular front ends, from Settings → Telemetry. |
Payload keys, on every action above: enabled, was_enabled, sources, was_sources
telemetry.config.updated— Collection is off unless an operator turned it on, and this row is when they did. It exists so that "was this hub recording its users at the time, and who decided that" is answerable afterwards — including by the people who were recorded.sourcesis empty when every front end is collected. A save that changed nothing emits no row.
notifications.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
notifications.config.updated |
config |
control-plane | beta | The webhooks the hub tells approvers through — when a grant request or an API token waits on them — are added, changed or removed, or the dashboard link they carry is, from Settings → Notifications. |
Payload keys, on every action above: webhooks, was_webhooks, url_changed, dashboard_url, was_dashboard_url
notifications.config.updated— A webhook decides where a stream about who asked for which credential leaves the hub, so this row is when that destination was chosen, and by whom. Each webhook is described asname (type) scheme://host, orsecret:<name>when its URL is kept in the secret store, with its events and allowed addresses — never the URL itself, which is the channel's credential.url_changednames the webhooks whose URL was replaced. A save that changed nothing emits no row.
disk.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
disk.floor.updated |
config |
control-plane | stable | The hub's free-space floor (orchestrator.min_free_disk_mb) is changed from Settings → Disk & Retention. |
Payload keys, on every action above: min_free_disk_mb, was_min_free_disk_mb, file
disk.floor.updated— 0 means the check is off: runs then start on any disk, and a task's outcome written onto a full one has no reserve to fall back on.filenames where the value was written — the hub's per-instance overlay or config.yaml. A save that changed nothing emits no row.
stt.credential.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
stt.credential.cleared |
config |
control-plane | stable | The speech-to-text API key is removed. |
stt.credential.set |
config |
control-plane | stable | The speech-to-text API key behind the Dictate button is configured. |
Payload keys, on every action above: scope
stt.credential.set— The key itself never reaches the payload. The row is filed against entity idstt.groq_api_key, so what changed is in the entity rather than the payload.
user.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
user.claude_home_swept |
user |
control-plane | stable | A hub member, as it starts and then hourly, removes from its own tree the Claude Code home of an identity the control plane denies hub-wide — a copy an offboarding did not reach. |
user.hold_place |
user |
control-plane | stable | A legal hold is placed on an identity, or its reason restated (cloop hub user hold), or an offboarding run with --keep-credentials records the hold it was given. |
user.hold_release |
user |
control-plane | stable | A legal hold is released. |
user.offboard |
user |
control-plane | stable | An identity is offboarded, summarising every surface the operation touched. |
user.offboard_claude |
user |
control-plane | stable | Offboarding ends the identity's Claude Code logins on every hub member: cancels one in flight, logs each home out and removes it. |
user.offboard_deny |
user |
control-plane | stable | Offboarding writes deny bindings so a stale IdP mapping cannot re-admit the identity. |
user.offboard_glasses |
user |
control-plane | stable | Offboarding revokes the identity's display-glasses links. |
user.offboard_grant |
user |
control-plane | stable | Offboarding revokes every live grant over the identity's personal secrets, under a legal hold too. |
user.offboard_hold |
user |
control-plane | stable | An offboarding runs under a legal hold (--keep-credentials): access is severed and the stored credentials are kept. |
user.offboard_lease |
user |
control-plane | stable | Offboarding releases secret leases held on the identity's behalf. |
user.offboard_membership |
user |
control-plane | stable | Offboarding drops the identity's project memberships, and the teams it was named on. |
user.offboard_project |
user |
control-plane | stable | Offboarding reports projects that need a new owner; it does not reassign them. |
user.offboard_request |
user |
control-plane | stable | Offboarding withdraws the self-service grant requests the identity left pending. |
user.offboard_secret |
user |
control-plane | stable | Offboarding destroys the identity's personal secrets through the broker. |
user.offboard_session |
user |
control-plane | stable | Offboarding revokes the identity's live sessions. |
user.offboard_task |
user |
control-plane | stable | Offboarding stops tasks the identity had running. |
user.offboard_token |
user |
control-plane | stable | Offboarding revokes the identity's API tokens. |
Payload keys:
user.claude_home_swept—identity,member,host,dir,denies,had_login,login_cancelled,logged_out,logout,removed,error,triggeruser.hold_place—identity,hold_reason,placed_at,reason,via,os_user,identity_input,subjects,emailsuser.hold_release—identity,hold_reason,placed_at,placed_by,reason,via,os_useruser.offboard—sessions,tokens,glasses,denies,leases,tasks,projects,memberships,teams,personal_secrets,personal_grants,grant_requests,claude_homes,legal_hold,warnings,identity,identity_input,subjects,emails,reason,viauser.offboard_claude—copies,removed,logged_out,logins_cancelled,unreached,kept,identity,identity_input,subjects,emails,reason,viauser.offboard_deny—count,bindings,claims,identity,identity_input,subjects,emails,reason,viauser.offboard_glasses—count,links,identity,identity_input,subjects,emails,reason,viauser.offboard_grant—count,grants,identity,identity_input,subjects,emails,reason,viauser.offboard_hold—secrets,claude_homes,grants_revoked,requests_withdrawn,identity,identity_input,subjects,emails,reason,viauser.offboard_lease—count,leases,identity,identity_input,subjects,emails,reason,viauser.offboard_membership—count,projects,teams,identity,identity_input,subjects,emails,reason,viauser.offboard_project—count,projects,action,identity,identity_input,subjects,emails,reason,viauser.offboard_request—count,requests,identity,identity_input,subjects,emails,reason,viauser.offboard_secret—count,secrets,action,identity,identity_input,subjects,emails,reason,viauser.offboard_session—count,sessions,identity,identity_input,subjects,emails,reason,viauser.offboard_task—count,tasks,identity,identity_input,subjects,emails,reason,via-
user.offboard_token—count,tokens,identity,identity_input,subjects,emails,reason,via -
user.claude_home_swept— One row per home,triggerstartuportimer.logoutsays why it was not logged out at Anthropic first — strict no-host-execution, a symbolic link — anderrorwhy it could not be removed, which the next sweep tries again. An identity under a legal hold, or denied only by a SCIM suspension still in force, keeps its home;cloop hub doctorreports the homes left on its host (offboard.claude_homes). user.hold_place— Every offboarding of the identity from then on — an operator's, the identity provider deleting the account, or ui.oidc.scim.offboard_after_days — keeps its personal secrets and Claude Code homes, as --keep-credentials does, and writes user.offboard_hold; so does every hub member's sweep of denied identities' Claude homes (Task 20422). An offboarding asked to keep credentials records its hold here before it denies anything, carrying the run's reason as hold_reason and the identity's resolved spellings.user.offboard_claude—copiesnames each home by member and directory with what became of it.unreachedlists hub members that could not be asked — a copy there may still hold a refresh token.user.offboard_grant— Each grant also has its ownsecret.revokerow from the broker, naming the reason.grantsnames each one with its secret and subject, which is where a shared project that loses the credential shows up.user.offboard_hold— Written whether or not anything was found to keep, so whether a hold was in force does not depend on what the person had stored.user.offboard_project— Deliberately a report rather than a mutation — picking a project's next owner is not a decision an offboarding script should make.user.offboard_request— Each request also has asecret.request_withdrawrow whose reason says it was withdrawn at offboarding rather than by its requester.user.offboard_secret— Each secret also has its ownsecret.deleterow. The sealed columns are overwritten before the row is deleted, and a tombstone keeps the name so a later lease can say which credential a project lost. Not written under a legal hold —user.offboard_holdsays what was kept instead.
scim.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
scim.credential_create |
scim_credential |
control-plane | stable | A SCIM bearer credential is minted for an identity provider. |
scim.credential_revoke |
scim_credential |
control-plane | stable | A SCIM credential is revoked, both its secrets at once. |
scim.credential_rotate |
scim_credential |
control-plane | stable | A SCIM credential's secret is replaced. |
scim.group_create |
scim_group |
control-plane | stable | The identity provider provisions a group. |
scim.group_delete |
scim_group |
control-plane | stable | The identity provider deletes a group. |
scim.group_members |
scim_group |
control-plane | stable | Members are added to or removed from a provisioned group. |
scim.group_update |
scim_group |
control-plane | stable | A provisioned group is renamed or its attributes change. |
scim.match_ambiguous |
scim_user |
control-plane | stable | A provisioned user's match value addresses more than one hub identity. |
scim.request_refused |
scim |
control-plane | stable | A request to /scim/v2 is refused before it reaches the directory. |
scim.user_create |
scim_user |
control-plane | stable | The identity provider provisions a user. |
scim.user_delete |
scim_user |
control-plane | stable | The identity provider deletes a user; the full offboarding runs first. |
scim.user_offboard_expired |
scim_user |
control-plane | stable | A user inactive for ui.oidc.scim.offboard_after_days is offboarded in full. |
scim.user_reactivate |
scim_user |
control-plane | stable | The identity provider reactivates a user (active: true). |
scim.user_suspend |
scim_user |
control-plane | stable | The identity provider deactivates a user (active: false). |
scim.user_suspend_incomplete |
scim_user |
control-plane | stable | A lease or task of a suspended user could not be ended after the suspension committed. |
scim.user_update |
scim_user |
control-plane | stable | The identity provider changes a provisioned user's attributes with PUT or PATCH. |
Payload keys:
scim.credential_create—id,name,reason,via,os_userscim.credential_revoke—id,name,reason,via,os_userscim.credential_rotate—id,name,grace_seconds,previous_valid_until,reason,via,os_userscim.group_create—id,display_name,external_id,members,unknown_membersscim.group_delete—id,display_name,external_id,membersscim.group_members—id,display_name,added,removed,unknown_members,methodscim.group_update—id,display_name,previous_display_name,external_id,methodscim.match_ambiguous—id,user_name,match_claim,match_value,subjects,emails,operationscim.request_refused—reason,method,path,ip,user_agent,client,suppressedscim.user_create—id,user_name,external_id,email,active,match_claim,match_valuescim.user_delete—id,user_name,identity,legal_hold,failuresscim.user_offboard_expired—id,user_name,identity,deactivated_at,legal_hold,failuresscim.user_reactivate—id,user_name,denies_lifted,denies_kept,grants_resumed,offboarded_at,notescim.user_suspend—id,user_name,identity,subjects,emails,usernames,oids,ambiguous,denies_written,denies_in_force,sessions,api_tokens,glasses,grants_suspended,leases,tasks,warningsscim.user_suspend_incomplete—id,user_name,failures-
scim.user_update—id,user_name,external_id,email,active,method,changed,match_claim,match_value -
scim.credential_create— The secret is shown once and stored only as an HMAC; no row carries it. scim.group_members— A removal takes effect at the member's next authorization decision: provisioned groups are read at every decision, not at the next claim refresh.scim.group_update— A rename changes what a scim_group role binding naming the old name grants.scim.match_ambiguous— The hub does not choose among them: it acts on the matched claim alone — a deny binding on it, and what carries it — and grants the user's provisioned groups to nobody.scim.request_refused— reason is unauthenticated, unknown, revoked, locked_out (a wrong credential past SCIM's own lockout), rate_limited, unavailable (the credential store could not be read) or sso_disabled. At most 30 rows a minute; suppressed counts the ones the limit did not write.scim.user_create— A user provisioned inactive is suspended at once, and gets a scim.user_suspend row too.scim.user_delete— The offboarding writes its own user.offboard* rows, with via scim.scim.user_offboard_expired— The offboarding writes its own user.offboard* rows, with via scim-expiry.scim.user_reactivate— Lifts only what the suspension wrote: a deny an administrator wrote for the same identity stays (denies_kept). Sessions, tokens, leases and tasks the suspension ended are not restored, and after an offboarding nothing is lifted.scim.user_suspend— Written in the transaction that writes the deny bindings, deletes the sessions, revokes the API tokens and glasses links and suspends the grants over the person's personal secrets. Leases and tasks are listed as planned; they end after the commit, and a failure there is scim.user_suspend_incomplete. Memberships and stored credentials are kept.
project.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
project.create |
project |
control-plane | beta | A project is created through the hub, or a creation is refused because of the directory it named. |
project.delete |
project |
control-plane | beta | A project is removed from the hub, or the hub refuses to delete its directory. |
project.firewall |
project |
control-plane | stable | A project's maintainer sets or clears its firewall rule set, or a tightened executor narrows it. |
Payload keys:
project.create—outcome,path,requested,name,owner,hub_made,projects_root,executor_id,rule,reasonproject.delete—outcome,path,name,owner,delete_root,root_deleted,hub_made,reason-
project.firewall—action,project_path,executor_id,from,to,fingerprint,cleared,reason -
project.create—outcomeis "created" or "refused".pathis the canonical directory andrequestedwhat the caller sent.hub_madesays this hub made the directory itself underprojects_root— the only directories ?delete_root=true may remove. A refusal carries theruleit broke (pkg/projectconfine: control_plane, hub, project, projects_root, outside_projects_root, relocated, unsafe, unverifiable, unresolvable, or taken for a path another creation holds) and the fullreason, which the caller may have been answered more vaguely than this: what exists at a path they cannot see is not theirs to learn. project.delete—outcomeis "removed" or "refused".delete_rootis what the caller asked for androot_deletedwhat happened; a refused one names itsreason— a directory the hub did not make, or one that now overlaps something it must not touch — and the project stays registered.project.firewall—actionis "set", "clear" or "constrain". A constrain row is written by the save that tightened a device or a virtual executor, against the identity that saved it, andreasonnames the executor and what had to be taken away. Stored in the control plane, where the rule set is.
project.member.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
project.member.change |
project_member |
control-plane | beta | A member's role on a project is changed. |
project.member.grant |
project_member |
control-plane | beta | An identity is added to a project's members at a role. |
project.member.leave |
project_member |
control-plane | beta | A member removes themselves from a project. |
project.member.revoke |
project_member |
control-plane | beta | A member is removed from a project, or the project itself is removed from the hub. |
Payload keys, on every action above: project, project_path, identity, team, role, previous_role, reason, via, left
project.member.change—previous_roleis the role replaced; a change in either direction is recorded.project.member.grant—viais "api" or "cli". A membership only adds access: the hub unions its role with whatever the identity already holds.project.member.leave— Separate from project.member.revoke so "was this person removed or did they leave" stays answerable.project.member.revoke—roleis the role the member held. A project removed from the hub drops its whole roster, one row per member, withreasonsaying so.
team.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
team.create |
team |
control-plane | beta | An administrator creates a team. |
team.delete |
team |
control-plane | beta | An administrator deletes a team. |
team.member.add |
team |
control-plane | beta | An administrator adds a person to a team by name. |
team.member.remove |
team |
control-plane | beta | An administrator takes a person off a team. |
team.update |
team |
control-plane | beta | An administrator renames a team, or changes the groups it follows. |
Payload keys, on every action above: team, name, idp_group, scim_group, previous, identity, members, shares, via, reason, os_user
team.create—viais "api" or "cli".idp_groupandscim_groupname the groups whose members are on the team without being added by name.team.delete—memberslists the people it named andsharescounts the projects it was shared with. Each of those shares is removed in the same commit and recorded as its own project.member.revoke, withreasonsaying the team was deleted.team.member.remove— Removes the person's named membership only: somebody on the team through its identity-provider or SCIM group stays on it until the group changes.team.update—previousholds the name and links replaced. Relinking a team changes who is on it, and so who reaches every project shared with it.
audit.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
audit.gap |
audit |
either | stable | A process could not append audit events to this chain, and records what was lost: with the next append that succeeds, at a periodic flush, at shutdown, or when a later process adopts the losses a crashed one left behind. |
audit.restored |
audit |
either | beta | cloop db restore puts a backup in place of a database, recorded in the chain it restored as that chain's next row. |
Payload keys:
audit.gap—lost_events,failed_appends,first_failed_at,last_failed_at,first_error,last_error,actions,reasons,recorded,host,pids,adopted_from-
audit.restored—backup,backup_sha256,backup_time,sidecar,restored_id,restored_row_hash,restored_time,superseded_id,superseded_row_hash,superseded_time,lost_events,reason,accepted_loss,lost_keys,key_fingerprint,checkpoint_record -
audit.gap— Home is whichever chain lost the events: a failed write to the hub's control-plane database is recorded there, a failed write to a project's database in that project's chain — the gap has to sit in the chain it is a gap in.actionscounts the lost events by action name andreasonsby cause (locked, readonly, full, io, closed, other), each a list of {name, events} so redaction cannot withhold a count.recordedsays what wrote the row (recovery, flush, shutdown, exit) andadopted_fromnames processes that exited owing it.cloop hub audit verifyreports a chain holding gap rows as intact with gaps and exits 3. audit.restored— Home is whichever chain was restored: the control plane's, or a project's restored with the same command — the record has to sit in the history it interrupts. The row takes the id after the restored head, an id the superseded checkpoints saw with another hash; the restore recordcloop db restoreseals to the checkpoint sinks pins it, and is what letscloop hub audit verify --checkpointsreport the restore as acknowledged rather than as a truncation.accepted_lossis true when the restore went ahead under --accept-secret-loss, andlost_keyslists the sealing keys whose material it gave up, each with the rows sealed under it.
audit.forward.*¶
| Action | Entity | Home | Stability | Fires when |
|---|---|---|---|---|
audit.forward.configured |
config |
control-plane | beta | The cluster leader starts forwarding under an audit.forward configuration that differs from the one it last recorded: a sink added, removed, or changed in where or how it sends. |
audit.forward.discontinuity |
audit |
control-plane | beta | A sink's cursor no longer lines up with the chain it tails — its row was pruned past, truncated away or rewritten — and the forwarder resumes from the last point that does. |
Payload keys:
audit.forward.configured—added,removed,changed,sinks,refused,previous_fingerprint,fingerprint,file-
audit.forward.discontinuity—sink,source,dir,reason,cursor_id,cursor_row_hash,resumed_from_id,resume_prev_hash,lost_rows -
audit.forward.configured— Detected rather than reported by an editor: the leader compares a fingerprint of the sinks it runs under — config.yaml merged with the hub's per-port overlay — against the one stored in the control plane, so an edit made with any tool, while the hub was down or up, is recorded by the next leader to run under it.sinksdescribes every sink in force (name, type, format, endpoint, the secret-store references it reads — names, never values — and whether it sends plaintext to loopback);added,removedandchangedare sink names;refusednames sinks the loader switched off. The row is forwarded like any other, so the sinks that remain see what changed about the ones that left. audit.forward.discontinuity—reasonis pruned (rows were pruned before the sink acknowledged them,lost_rowsof them — a prune forced past the sink's cursor; they are in the prune's sealed archive, not at the sink), truncated (the chain now ends before the cursor), rewritten (the cursor's row has another hash), or broken (the cursor's row is intact and what follows it does not continue it:lost_rowsrows are missing inside the chain, or a row was relinked; the rows are forwarded as they stand). For truncated and rewritten the forwarder resends from the newest point it acknowledged that the chain still holds, so the sink receives the chain as it now stands beside what it already has: two rows with one id and different hashes are the evidence, andcloop hub audit verify --checkpointsnames the truncation. Recorded in the control plane's chain whichever chain broke, because the cursor is the control plane's.