Common publish rejections
Every error and warning the marketplace's pre-publish quality gate can return. Each entry names the machine code (as it appears in your CLI output or /listings/lint response), the cause, and the exact fix. Publish is gated on the errors here — warnings surface as admin_notes but don't block.
POST /listings (errors → 400 · warnings → auto_lint admin_note) and locally in the CLI at railcall market publish (via a pre-flight call to POST /listings/lint) so you catch errors before spending a signature. The browser publish form pre-flights too — you'll see findings inline before submit.Workflow errors (block publish)
spec.no_nodes
The nodes[] and steps[] arrays are both empty or missing. A workflow with zero nodes has nothing to do and can't receive human review either. Add at least one node.
spec.no_edges
The workflow has >1 node but edges[] is empty — no execution DAG declared, so workflow_engine can't know what runs after what. Wire the nodes together:
{
"nodes": [
{ "id": "intake", "type": "transform" },
{ "id": "approve", "type": "effect", "provider": "slack", "args": {"text": "…"} },
{ "id": "charge", "type": "effect", "provider": "stripe", "args": {"amount_cents": "{{intake.output.total_cents}}"} }
],
"edges": [
{ "from": "intake", "to": "approve" },
{ "from": "approve", "to": "charge" }
]
}spec.postgres_missing_sql
A node with provider: "postgres" is missing the sql (or query) arg. The postgres provider expects an actual statement — a placeholder{"note": "…"} arg fails at plan time on any real station. Ship the INSERT/SELECT:
{
"id": "audit_ledger",
"type": "effect",
"provider": "postgres",
"args": {
"sql": "INSERT INTO audit_ledger (workflow_id, receipt_hash, executed_at) VALUES ({{workflow_id}}, {{receipt.integrity_hash}}, NOW())"
}
}spec.stripe_hardcoded_amount
A stripe node has a fixed numeric amount_cents (e.g. 500000). That charges the same amount every run regardless of input — almost certainly a template-generation artefact. Bind the amount to context or an upstream node's output:
- "args": { "amount_cents": 500000, "currency": "usd" }
+ "args": { "amount_cents": "{{intake.output.invoice_total_cents}}", "currency": "usd" }spec.duplicate_args
Two or more nodes share an identical string arg (≥20 chars). Usually the approval_gate and notify_stakeholdernodes ended up with the same Slack text because they were generated from one template. Different nodes need different content — the notify step should say "Payment executed, receipt X" not repeat the approval prompt.
Workflow warnings (persist as admin_notes)
spec.timer_missing_duration
A wait/timer node has no wait_ms / wait_seconds / wait_until / duration_ms arg. Either specify how long, or drop the node.
spec.description_promises_branching
The description mentions matching / reconciliation / verification / approval-gate logic but the spec has no branching edges (edges.length ≤ nodes.length). Either add the conditional edges the description implies, or rewrite the description to match what the spec actually does. Aspirational descriptions get refunded aggressively.
spec.paid_listing_thin_description
A paid listing (price_cents > 0) has a description under 40 words. Buyers need more to trust a paid purchase. Add sections on what it does, when to use it, and what the prerequisites are.
Module errors (block publish)
module.manifest_unparseable
The module.jsonin your payload isn't valid JSON. Common cause: trailing comma, unquoted keys, or smart-quote characters copied from a doc. Run through python3 -m json.tool module.json locally.
module.no_commands
commands[] in the manifest is empty. A module with zero commands is a shell — every module needs at least one command exposing what it does.
module.command_missing_id
module.command_missing_title
A command entry is missing id (a non-empty string) or title (≥3 chars). The title becomes the palette row label buyers see — set it deliberately.
module.command_missing_input_schema
A command's input_schemais present but not a dict (e.g. it's an array or a string). If the command takes no inputs, omit the field entirely. Otherwise use the flat RailCall shape:
{
"id": "notion.page_create",
"title": "Create a Notion page",
"input_schema": {
"title": { "type": "string", "required": true },
"body": { "type": "string", "required": false },
"database_id":{ "type": "string", "required": true }
}
}module.handler_missing
module.handler_trivial
Your handlers/handler.py is either missing from the payload or under 200 bytes — almost always a placeholder shell like def _placeholder(): pass. Ship the real implementation.
module.handler_missing_function
commands: 0in Studio's modules list even though signature verification passes.Each command id in the manifest maps to a Python function via:
fn_name = command_id.replace('.', '_').replace('-', '_')The loader looks for that exact function in handlers/handler.py. Miss it and the command is silently rejected — but the module signature still verifies fine (the signature covers the bundle bytes, not the internal wiring).
Concrete example:
// module.json
{
"commands": [
{ "id": "notion.page_create", "title": "Create a Notion page" },
{ "id": "notion.page-update", "title": "Update a Notion page" }
]
}# handlers/handler.py — function names MUST be these:
def notion_page_create(inputs, stamp):
...
def notion_page_update(inputs, stamp): # note: '-' becomes '_' too
...To debug on an existing install, hit http://127.0.0.1:8799/api/modules/list and look at rejected[]— it names the exact function the loader expected but couldn't find.
Module warnings (persist as admin_notes)
module.description_command_count_mismatch
Description claims a number of operations that doesn't match commands[].length— e.g. "Ships 40 Salesforce operations" but the manifest declares 3. Either add the missing commands or rewrite the description to match what actually ships.
Diagnose without publishing
Post your payload to the check-only lint endpoint to see every finding without touching the pending_review queue:
curl -sS https://railcall-marketplace-lggm.onrender.com/listings/lint \
-H "content-type: application/json" \
-d @- <<EOF
{
"listing_type": "workflow",
"payload": $(cat spec.json),
"description": "…",
"price_cents": 0
}
EOFResponse shape: { ok, errorCount, warningCount, findings: [{severity, code, message, hint, path}] }. Rate-limited to 60 calls / minute per IP — plenty of headroom to iterate on a spec.