About actions
A RevOS MCP server skill for inspecting integration actions and diagnosing their runs. It guides agents from run results and organization-specific error instructions to the cause, while limiting unnecessary payload reads.
Key features
- 01Inspect action catalogue and details
- 02List and inspect action runs
- 03Read organization error instructions
- 04Trace failures from run results
- 05Run an action column when requested
Use cases
- 01Find recent failures for a record
- 02Explain an action run error
- 03Check an action's expected inputs
- 04Execute an action column on a row
MCP support
Uses the RevOS MCP server's generic api_read and api_write tools to access actions and runs. It describes the exposed action-run operations and their cautious use.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ revosai/ skills --skill actionsAfter installing, type/actionsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
evals/evals.jsonreferences/debug.mdrun.mdsync-now.mdDetails
From the project READMEActions
Three things share the name, and telling them apart is most of the work:
- An action is one operation of an integration against one object:
"NetSuite upsert customer", "Enrich name". The catalogue is the
actionsresource; each has anidand anintegrationName. - An action column is an action wired into a table: which action, its
params, and theinputmapping that says which of the row's columns feed it. It lives in the table'sobjectsColumns(the tables skill covers adding and changing one). - An action run is one execution of one action column against one row.
A run is stored once an attempt finishes, so its status is always
COMPLETEDorFAILED(the API's enum listsACTIVE,DELAYED,WAITINGtoo, but no run is saved with them). Runs are kept for 14 days.
You reach them through the RevOS MCP server's generic tools:
| Call | What it does |
|---|---|
api_read { "resource": "actions", "params": { "fields": "id,name,integrationName" } } | the catalogue; add "onlyUsed": "true" for the ones a table uses |
api_read { "resource": "actions", "id": "<action id>" } | one action: name, description, integrationName |
api_read { "resource": "actions", "id": "<action id>", "method": "errorInstructions" } | what this organization wrote about that action's errors |
api_read { "resource": "action-runs", "params": { … } } | list runs: filter, orderBy, fields, pageSize |
api_read { "resource": "action-runs", "id": "<run id>" } | one run, with its input and result |
api_write { "resource": "action-runs", "data": { "tableId": "…", "columnId": "…" } } | run an action column — see references/run.md first |
The calls in this skill are complete as written: go straight to them.
api_search and api_details are for when one of them is refused, or you
need a filter or field this skill doesn't name.
Something failed: start from the run
Most questions here are "why did this fail". Work in levels and stop at the first one that answers the question: a failure whose cause is already named in the run's own result doesn't need the table's schema, and pulling it anyway costs the user a slower answer buried in detail they didn't ask for.
Find the run. The user may hand you a run id straight from the
action-run drawer in the UI. More often they name a record — "deal
31415926535", a company, a contact. That id is the row's objectId: a row
usually carries the id its record has in the source system. It is not
unique: the same record can be a row in several tables, and a row has an
action column per action (a sync to NetSuite next to a write-back to
HubSpot), each with its own runs. So list the record's newest runs first,
without their payloads (one run's input and result can be thousands of
tokens):
api_read { "resource": "action-runs",
"params": { "filter": "objectId == \"<record id>\"",
"orderBy": "createdAt desc", "pageSize": 5,
"fields": "id,actionId,columnId,modelId,objectName,status,createdAt" } }
Add && modelId == "<table id>" to the filter when you know the table.
Then take the newest run of the action column the question is about
(modelId, columnId) and read that run by id. If it is COMPLETED, the
record is not failing any more: say so. If the runs span several tables or
action columns and the request doesn't say which one ("to NetSuite"), name
them and ask.
With neither a run id nor a record, list the failures without the payloads:
api_read { "resource": "action-runs",
"params": { "filter": "status == \"FAILED\" && modelId == \"<table id>\"",
"orderBy": "createdAt desc", "pageSize": 10,
"fields": "id,actionId,columnId,modelId,objectId,objectName,status,attemptsMade,createdAt" } }
modelId is the table's id. Narrow with columnId or objectId when you
know them.
Read the run by id. One call, and it carries the spine as well as both
payloads: actionId, columnId, modelId, objectId, objectName,
status, attemptsMade, input and result.
Read the organization's error instructions for the run's actionId.
This is the next call after the run, every time, however clear the error
already looks: api_read on actions with that id and
method: "errorInstructions", described
below. They often map the error
message straight to its cause and fix, and say who fixes it.
Where the error lives. There is no separate error column. result
holds the whole run result, a union discriminated by metadata.success. On
a failure it carries:
metadata.error.message— the message to trust and to quote. When it is a bare HTTP status line ("Request failed with status code 400"), the vendor's own message is inerror.details.response.data; quote that;metadata.error.aiMessage— an AI rewording generated automatically when the failure was saved (paid plans, under a daily cap). The table cell shows it, as❌ <aiMessage>, so error text the user copied from a cell is usually this rewording, not the system's message. Never present it as the system's own error;error— a normalized object withmessage,code,details,isUnrecoverableandretryAfter.
error.isUnrecoverable: true means the framework stopped retrying: any HTTP
4xx other than 429, input that failed validation, or an action that said so.
Re-running unchanged fails the same way; it can work once the cause is
fixed.
A large share of failures are fully explained right here — credentials the
target system rejected, a permission, a validation message
that already names the field and the reason. When result, read together
with the organization's instructions, says something the user can act on,
that is the answer: quote it, say what to do, stop.
When the id doesn't resolve. api_read by id answers data: null
rather than a 404. (The id may also be the run's jobId, which the cell's
metadata.jobId carries — both resolve.) A table cell goes on showing a failure
long after the run behind it was removed, so an id can name a run older
than the 14-day window. Say so and stop — no page of action-runs can
contain it. If the user quoted the error text, that text is still worth
reading back to them. Then offer what does work: re-run the action and
diagnose the fresh failure.
When the run alone doesn't answer — the error is about a value that was missing, empty or malformed, or the user asks where a value should have come from — go on to references/debug.md: the mapping, the lineage, and how to say where the fault is.
The organization's error instructions
Organizations write down what their actions' errors mean and how they are
fixed: what a particular NetSuite error really means for them, who fixes
what, how a link to their CRM is built. Read them once for each failed run
you diagnose, by the run's actionId:
api_read { "resource": "actions", "id": "<the run's actionId>", "method": "errorInstructions" }
It returns three texts, each null when nothing was written:
global— applies to every action in this organization;integration— every action of this action's integration;action— this action alone.
They add up rather than override one another: the action-level text assumes you've read the integration-level one.
They are where a convention that makes your obvious recommendation wrong
lives. When they give wording, a link format, or an owner for the fix, use
theirs. If all three are null, the organization wrote nothing for this
action; carry on without them. If the read itself is refused, don't retry:
carry on from the run, and say you couldn't read them.
Running an action
Running acts on connected systems — it creates and updates records in the
customer's CRM or ERP — so only do it when the user asks, and read
references/run.md before the first api_write on
action-runs.
Syncing
"Sync" means two things: ingestion, a connection pulling a source
system into RevOS ("sync HubSpot"), and activation, an action pushing a
row out ("sync this deal to NetSuite"). Ingestion is the connections
skill. For one record synced now from an id or a link, check the record in
its cube first. Ingest its source only if RevOS doesn't have it or the user
just changed it there, then run the action for that one row. Read
references/sync-now.md for that.
Adding or changing an action column
That is a change to the table, not to the action: the tables skill, its
column reference. The catalogue here tells you which actionId to use and,
through the action's description, what input it expects.
Reporting back
Lead with the cause in the user's terms, then what to do about it and who
does it. Quote metadata.error.message verbatim when the fix involves a
third party — a paraphrased vendor error is worse than useless when the
user takes it to that vendor's support. Name the row by objectName, not
by id. Don't dump input or result.