
Rust Backend
Apply Windmill backend Rust conventions—Error types, explicit SQLx columns, batch queries—when patching windmill-labs/windmill server code.
Overview
Rust-backend is an agent skill for the Build phase that enforces Windmill backend Rust patterns for errors, SQLx queries, and JSON handling.
Install
npx skills add https://github.com/windmill-labs/windmill --skill rust-backendWhat is this skill?
- Mandatory `windmill_common::error::Error` and `Result`/`JsonResult` patterns—no panics in library code
- SQLx rule: never SELECT *—explicit column lists for worker/API version skew
- Batch queries with ANY($1) to avoid N+1 database access
- JSON guidance favoring Box<RawValue> over serde_json::Value when not inspecting payloads
- Explicit ban on SELECT * for SQLx job queries
- Library code must not panic; prefer Result over unwrap
Adoption & trust: 529 installs on skills.sh; 16.7k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are changing Windmill backend Rust without the repo’s Error, SQLx, or JSON conventions and risk breaking workers or compatibility.
Who is it for?
Contributors or maintainers shipping features and fixes in windmill-labs/windmill `backend/` with SQLx and async Rust.
Skip if: Greenfield Rust apps outside Windmill, frontend Windmill scripts, or teams that only consume Windmill SaaS without touching server code.
When should I use this skill?
Writing or modifying Rust code in the Windmill backend directory.
What do I get? / Deliverables
After the skill runs, patches use explicit SQLx columns, shared Error returns, batch queries, and RawValue-friendly JSON consistent with Windmill backend norms.
- Backend Rust changes following Windmill Error and SQLx rules
- Review-ready PR diffs without SELECT * or panic paths
Recommended Skills
Journey fit
Backend implementation for Windmill contributors sits squarely in Build when extending jobs, workspaces, and API handlers. Canonical shelf is backend because the skill governs Rust in `backend/` only, not frontend scripts or deploy playbooks.
How it compares
Windmill-specific backend guardrails—not generic Clippy or Rust API guidelines for any crate.
Common Questions / FAQ
Who is rust-backend for?
Developers and agent users modifying the Windmill open-source backend who need enforced SQLx and error-handling patterns.
When should I use rust-backend?
Use during Build/backend whenever you add routes, job logic, or database access under the backend directory—especially before opening a PR.
Is rust-backend safe to install?
It instructs code changes only; review the Security Audits panel on this Prism page and follow your org’s policy for third-party agent skills.
SKILL.md
READMESKILL.md - Rust Backend
# Windmill Rust Patterns Apply these Windmill-specific patterns when writing Rust code in `backend/`. ## Error Handling Use `Error` from `windmill_common::error`. Return `Result<T, Error>` or `JsonResult<T>`: ```rust use windmill_common::error::{Error, Result}; pub async fn get_job(db: &DB, id: Uuid) -> Result<Job> { sqlx::query_as!(Job, "SELECT id, workspace_id FROM v2_job WHERE id = $1", id) .fetch_optional(db) .await? .ok_or_else(|| Error::NotFound("job not found".to_string()))?; } ``` Never panic in library code. Reserve `.unwrap()` for compile-time guarantees. ## SQLx Patterns **Never use `SELECT *`** — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version: ```rust // Correct sqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id) // Wrong — breaks when columns are added sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id) ``` Use batch operations to avoid N+1: ```rust // Preferred — single query with IN clause sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await? ``` Use transactions for multi-step operations. Parameterize all queries. ## JSON Handling Prefer `Box<serde_json::value::RawValue>` over `serde_json::Value` when storing/passing JSON without inspection: ```rust pub struct Job { pub args: Option<Box<serde_json::value::RawValue>>, } ``` Only use `serde_json::Value` when you need to inspect or modify the JSON. ## Serde Optimizations ```rust #[derive(Serialize, Deserialize)] pub struct Job { #[serde(skip_serializing_if = "Option::is_none")] pub parent_job: Option<Uuid>, #[serde(skip_serializing_if = "Vec::is_empty")] pub tags: Vec<String>, #[serde(default)] pub priority: i32, } ``` ## Async & Concurrency Never block the async runtime. Use `spawn_blocking` for CPU-intensive work: ```rust let result = tokio::task::spawn_blocking(move || expensive_computation(&data)).await?; ``` **Mutex selection**: Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) for data protection. Only use `tokio::sync::Mutex` when holding locks across `.await` points. Use `tokio::sync::mpsc` (bounded) for channels. Avoid `std::thread::sleep` in async contexts. ## Module Structure & Visibility - Use `pub(crate)` instead of `pub` when possible - Place new code in the appropriate crate based on functionality - API endpoints go in `windmill-api/src/` organized by domain - Shared functionality goes in `windmill-common/src/` ## Code Navigation Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths. ## Axum Handlers Destructure extractors directly in function signatures: ```rust async fn process_job( Extension(db): Extension<DB>, Path((workspace, job_id)): Path<(String, Uuid)>, Query(pagination): Query<Pagination>, ) -> Result<Json<Job>> { ... } ```