<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammad Awais</title>
    <description>The latest articles on DEV Community by Muhammad Awais (@awaismehr).</description>
    <link>https://dev.to/awaismehr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4038391%2F16e6b2b7-bcab-4f6f-91ff-17345426f723.jpg</url>
      <title>DEV Community: Muhammad Awais</title>
      <link>https://dev.to/awaismehr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/awaismehr"/>
    <language>en</language>
    <item>
      <title>We Gave Claude Code Access to Our Production Database. Here's How It Doesn't Go Wrong.</title>
      <dc:creator>Muhammad Awais</dc:creator>
      <pubDate>Sat, 08 Aug 2026 09:27:33 +0000</pubDate>
      <link>https://dev.to/awaismehr/we-gave-claude-code-access-to-our-production-database-heres-how-it-doesnt-go-wrong-1md7</link>
      <guid>https://dev.to/awaismehr/we-gave-claude-code-access-to-our-production-database-heres-how-it-doesnt-go-wrong-1md7</guid>
      <description>&lt;h1&gt;
  
  
  We Gave Claude Code Access to Our Production Database. Here's How It Doesn't Go Wrong.
&lt;/h1&gt;

&lt;p&gt;Every team building with AI agents eventually hits the same wall: the agent needs to query a real database to be useful, and every way of doing that looks bad. A &lt;code&gt;DATABASE_URL&lt;/code&gt; in the agent's config is a credential sitting on a laptop. A shared read replica with a shared login means "who ran that query" is unanswerable. And a hand-rolled MCP wrapper around your DB driver is a security review waiting to happen. We built &lt;code&gt;db-mcp-gateway&lt;/code&gt; to make that a solved problem instead of a judgment call. It's a self-hosted, MIT-licensed gateway, written in Rust,that sits between your AI agents and your databases. The agent never holds a credential not once, not cached, not in a log line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-minute model
&lt;/h2&gt;

&lt;p&gt;Deploy the gateway once. Developers add one URL to their MCP config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http db-gateway &lt;span class="nt"&gt;--scope&lt;/span&gt; project https://db.internal.acme.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire client-side setup. First call to any gateway tool returns&lt;br&gt;
&lt;code&gt;401&lt;/code&gt;, the agent surfaces an SSO login link, you authenticate through your&lt;br&gt;
org's actual identity provider (Okta, Google Workspace, Entra, Authentik,&lt;br&gt;
Keycloak), and the token lands in your system keychain — never in a config&lt;br&gt;
file, never in the agent's context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────┐    MCP/HTTPS    ┌──────────────┐    pg wire    ┌──────────┐
│ agent   │ ──────────────▶ │   gateway    │ ────────────▶ │ target   │
│ (Claude │   bearer: jwt   │              │  ro role per  │  DBs     │
│  Code)  │ ◀────────────── │  authz+audit │ ◀──────────── │          │
└─────────┘   tool result   └──────┬───────┘   result rows └──────────┘
                                   │
                                   ▼
                            ┌──────────────┐
                            │ state DB     │
                            │ (sessions +  │
                            │  audit log)  │
                            └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Permissions are YAML, reviewed by PR not a UI
&lt;/h2&gt;

&lt;p&gt;There's deliberately no admin UI. Access lives in a config file your team&lt;br&gt;
reviews the same way you review any other production change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data-analysts&lt;/span&gt;        &lt;span class="c1"&gt;# matches an SSO group from the IdP claim&lt;/span&gt;
    &lt;span class="na"&gt;grants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_read&lt;/span&gt;      &lt;span class="c1"&gt;# SELECT + schema reads, never writes&lt;/span&gt;
        &lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;require_reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# agent must justify the query → audit log&lt;/span&gt;
          &lt;span class="na"&gt;row_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;       &lt;span class="c1"&gt;# gateway truncates beyond this&lt;/span&gt;
          &lt;span class="na"&gt;statement_timeout_ms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actions are hierarchical (&lt;code&gt;query_write&lt;/code&gt; ⊇ &lt;code&gt;query_read&lt;/code&gt; ⊇ &lt;code&gt;schema_read&lt;/code&gt;), and&lt;br&gt;
when multiple grants match, the most restrictive constraint wins. Writes are&lt;br&gt;
opt-in per grant and, even then, capped hard: a &lt;code&gt;query_write&lt;/code&gt; grant permits&lt;br&gt;
a single &lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt; per call, through the same timeout and&lt;br&gt;
row-cap path as a read. &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;ALTER&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, &lt;code&gt;GRANT&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;REVOKE&lt;/code&gt;, and multi-statement bodies are rejected with &lt;code&gt;forbidden_sql&lt;/code&gt; —&lt;br&gt;
always, regardless of grant. The gateway never issues DDL, full stop.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the agent actually sees
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    What databases can I see through db-gateway?

Claude: [calls list_servers → list_databases]

        You have access to:
        • staging (postgres) — staging/app, staging/billing
        • prod (postgres)    — prod/billing (read-only, reason required)

You:    How many users signed up in staging/app over the last 7 days?

Claude: [calls run_query staging/app]
          select count(*) from users where created_at &amp;gt; now() - interval '7 days';

        → 412 (returned in 38ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Cross a boundary and the failure is explicit, not silent: &lt;code&gt;forbidden&lt;/code&gt; if&lt;br&gt;
your group's grant doesn't cover it, &lt;code&gt;timeout&lt;/code&gt; if the query exceeds&lt;br&gt;
&lt;code&gt;statement_timeout_ms&lt;/code&gt;, &lt;code&gt;row_limit_exceeded&lt;/code&gt;/&lt;code&gt;truncated: true&lt;/code&gt; if the result&lt;br&gt;
is bigger than the grant allows, or a &lt;code&gt;reason_required&lt;/code&gt; prompt if the grant&lt;br&gt;
demands one — typically anything touching prod. The agent asks you for the&lt;br&gt;
reason and it lands verbatim in the audit row. "checking stuff" versus&lt;br&gt;
"verifying SUPPORT-4421 root cause" is the difference between an audit log&lt;br&gt;
that's useless in six months and one that isn't.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;run_query&lt;/code&gt; call writes that audit row user, SQL, reason, row&lt;br&gt;
count, duration, outcome — &lt;em&gt;before&lt;/em&gt; the result comes back to the agent, not&lt;br&gt;
after. Hot retention lives in Postgres; there's an optional archive sink to&lt;br&gt;
S3/GCS/Azure and streaming export via OTLP/syslog/stdout for teams who&lt;br&gt;
already have a SIEM.&lt;/p&gt;
&lt;h2&gt;
  
  
  The stack, if you're curious
&lt;/h2&gt;

&lt;p&gt;Rust, tokio for async, axum for HTTP, sqlx for the DB layer, config in&lt;br&gt;
YAML validated at boot with &lt;code&gt;line:column&lt;/code&gt; error pointers on typos. The&lt;br&gt;
gateway also speaks the MCP Authorization spec (OAuth 2.1 + PKCE) itself —&lt;br&gt;
it &lt;em&gt;is&lt;/em&gt; the authorization server, brokering your IdP login internally, so&lt;br&gt;
Claude Code and other MCP clients authenticate with zero manual credential&lt;br&gt;
wiring on the client side.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's not done yet
&lt;/h2&gt;

&lt;p&gt;In the interest of not doing the thing every "launch" post does: this is a&lt;br&gt;
young project (first commit May 2026) and a few pieces referenced in the&lt;br&gt;
docs aren't real yet. There's no Helm chart docker-compose or hand-rolled&lt;br&gt;
k8s manifests today. &lt;code&gt;vault:&lt;/code&gt;/&lt;code&gt;aws-sm:&lt;/code&gt;/&lt;code&gt;gcp-sm:&lt;/code&gt; secret backends are&lt;br&gt;
recognized in config but rejected at boot until they land; use &lt;code&gt;${ENV:...}&lt;/code&gt;&lt;br&gt;
or &lt;code&gt;${FILE:...}&lt;/code&gt; refs for now. Session revocation is a manual &lt;code&gt;psql&lt;/code&gt; insert&lt;br&gt;
into a denylist table until the planned &lt;code&gt;gateway admin&lt;/code&gt; CLI ships. None of&lt;br&gt;
that blocks running it in production we do but it's not the finished&lt;br&gt;
enterprise product yet, and I'd rather say that here than have someone find&lt;br&gt;
out from the roadmap doc after trying it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this exists
&lt;/h2&gt;

&lt;p&gt;db-mcp-gateway is built and maintained largely by AI agents at&lt;br&gt;
developerz.ai, under human review, with every change going through CI&lt;br&gt;
before merge. It's also, not coincidentally, a small working demo of the&lt;br&gt;
same idea developerz.ai sells as a product: agents should never hold raw&lt;br&gt;
credentials, and every action they take should be attributable and&lt;br&gt;
auditable. The gateway is the free, self-hosted version of that philosophy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull ghcr.io/developerz-ai/db-mcp-gateway:1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MIT-licensed. Repo, docs, and the full permissions reference are at&lt;br&gt;
github.com/developerz-ai/db-mcp-gateway.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>You Don't Need Sidekiq Pro for Batch Jobs, Rate Limiting, or Cron Here's a Free Drop-In</title>
      <dc:creator>Muhammad Awais</dc:creator>
      <pubDate>Fri, 07 Aug 2026 15:37:19 +0000</pubDate>
      <link>https://dev.to/awaismehr/you-dont-need-sidekiq-pro-for-batch-jobs-rate-limiting-or-cron-heres-a-free-drop-in-4l6</link>
      <guid>https://dev.to/awaismehr/you-dont-need-sidekiq-pro-for-batch-jobs-rate-limiting-or-cron-heres-a-free-drop-in-4l6</guid>
      <description>&lt;p&gt;If you've built anything non-trivial with Sidekiq, you've probably hit the wall where the feature you actually need batch job callbacks, rate limiting,scheduled jobs lives behind Sidekiq Pro or Enterprise. That's a reasonable business model, and it's funded years of full-time development on Sidekiq. If your company can afford the license, it's worth paying for. But a lot of teams can't justify the line item for a side project or an early startup. So I want to show what those features actually look like, and then show&lt;br&gt;
a free, MIT-licensed, wire-compatible way to get them: &lt;a href="https://github.com/developerz-ai/wurk" rel="noopener noreferrer"&gt;wurk&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/developerz-ai" rel="noopener noreferrer"&gt;
        developerz-ai
      &lt;/a&gt; / &lt;a href="https://github.com/developerz-ai/wurk" rel="noopener noreferrer"&gt;
        wurk
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Drop-in replacement for Sidekiq + Pro + Enterprise. 100% API-compatible, free forever, meaningfully faster. Mountable Rails engine.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/developerz-ai/wurk/main/docs/assets/wurk-logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdeveloperz-ai%2Fwurk%2Fmain%2Fdocs%2Fassets%2Fwurk-logo.png" alt="Wurk — an orc ready to work" width="220"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Wurk ⚡&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Wurk, wurk.&lt;/strong&gt; 🪓 &lt;em&gt;Ready to work. Zug zug.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 100% drop-in replacement for Sidekiq + Sidekiq Pro + Sidekiq Enterprise. Free forever.&lt;/strong&gt;&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;a href="https://wurk.demo.developerz.ai/wurk" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/77f17c9dccf1c5276ea4b1d385aee372af458b519131e93719eede49228813c9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c69766525323064656d6f2d7775726b2e64656d6f2e646576656c6f7065727a2e61692d3232633535653f6c6f676f3d676f6f676c656368726f6d65266c6f676f436f6c6f723d7768697465" alt="Live Demo"&gt;&lt;/a&gt;
&lt;a href="https://rubygems.org/gems/wurk" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/edb4c6145df1b7c2fbe78bb8c187a411f08f6e86c0ea837d540ac9bcf812717a/68747470733a2f2f696d672e736869656c64732e696f2f67656d2f762f7775726b2e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/developerz-ai/wurk/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/developerz-ai/wurk/actions/workflows/test.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/developerz-ai/wurk/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4fe2d430061cde5dae65efcaa5c4b0cb04cc3a67beea9414db452cc938a1cdcb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f766572616765253230676174652d6c696e6525323025453225383925413539302532352d627269676874677265656e2e737667" alt="Coverage gate"&gt;&lt;/a&gt;
&lt;a href="https://www.ruby-lang.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b6134d848b6e979304693a71f7ceb683071764ae7cdbf2ed133121b4ca379520/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727562792d254532253839254135253230332e322d4343333432442e737667" alt="Ruby"&gt;&lt;/a&gt;
&lt;a href="https://github.com/developerz-ai/wurk/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Wurk is wire-compatible with Sidekiq — same Redis keys, same job JSON, same Ruby DSL. Swap one line in your &lt;code&gt;Gemfile&lt;/code&gt; and your existing jobs, batches, limiters, cron entries, and live Redis data keep working untouched. The Pro and Enterprise feature sets ship in the same free gem, with no license check and no tiers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On speed:&lt;/strong&gt; Wurk is not currently faster than stock Sidekiq — it runs at roughly 0.87×–1.02× depending on workload shape, with parity on CPU and I/O but still behind on framework overhead (noop) and boot time. Numbers, method, and the reproduction command are in &lt;a href="https://github.com/developerz-ai/wurk/docs/benchmarks.md" rel="noopener noreferrer"&gt;docs/benchmarks.md&lt;/a&gt;; run them yourself with &lt;code&gt;rake bench:vs_sidekiq&lt;/code&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# Gemfile&lt;/span&gt;
&lt;span class="pl-en"&gt;gem&lt;/span&gt; &lt;span class="pl-s"&gt;"wurk"&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-diff notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; ...or drop in over an existing Sidekiq stack — delete these, add&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/developerz-ai/wurk" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  The migration is one line
&lt;/h2&gt;

&lt;p&gt;Wurk speaks the same Redis protocol as Sidekiq same keys, same job JSON, same&lt;br&gt;
Ruby DSL. If you're already on Sidekiq:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- gem "sidekiq"
- gem "sidekiq-pro", source: "https://gems.contribsys.com/"
- gem "sidekiq-ent", source: "https://enterprise.contribsys.com/"
&lt;/span&gt;&lt;span class="gi"&gt;+ gem "wurk"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bundle install&lt;/code&gt;, restart, done. &lt;code&gt;Sidekiq::Worker&lt;/code&gt;, &lt;code&gt;Sidekiq.configure_server&lt;/code&gt;,&lt;br&gt;
your existing job classes — none of it changes. Your in-flight Redis data keeps&lt;br&gt;
working untouched.&lt;/p&gt;
&lt;h2&gt;
  
  
  Batches, without the license check
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Sidekiq::Batch&lt;/code&gt; lets you fire off a group of jobs and run a callback when the&lt;br&gt;
whole group finishes — the classic use case is "process these 500 records, then&lt;br&gt;
send one summary email."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Import customer CSV"&lt;/span&gt;
&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MyCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"to"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ops@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;ImportRowJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batches nest, too — a parent batch's callback only fires once every child batch&lt;br&gt;
has finished, which is what you want for multi-stage pipelines. In stock&lt;br&gt;
Sidekiq this whole API is Pro-only. In wurk it's just... there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rate limiting five different ways
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Sidekiq::Limiter&lt;/code&gt; — an Enterprise feature — ships five limiter types, because&lt;br&gt;
"rate limit" means different things depending on what you're protecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# concurrent: at most N jobs of this type running at once&lt;/span&gt;
&lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"api-calls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# window: at most N calls per rolling time window&lt;/span&gt;
&lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"api-calls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# leaky bucket, bucket, and points limiters cover the rest —&lt;/span&gt;
&lt;span class="c1"&gt;# see docs/rate-limiting.md for when to reach for which&lt;/span&gt;
&lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within_limit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ThirdPartyApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever hand-rolled a Redis &lt;code&gt;INCR&lt;/code&gt; + &lt;code&gt;EXPIRE&lt;/code&gt; rate limiter and hit an&lt;br&gt;
edge case at 2am, this is the version that's already handled the edge cases.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cron jobs that actually run once
&lt;/h2&gt;

&lt;p&gt;Periodic jobs are the other Enterprise feature, and the part that's easy to get&lt;br&gt;
wrong yourself: if you run multiple worker processes, a naive cron&lt;br&gt;
implementation fires the job &lt;strong&gt;once per process&lt;/strong&gt;, not once total.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Periodic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DailyReportJob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wurk's periodic scheduler is leader-elected across the cluster, so this fires&lt;br&gt;
exactly once regardless of how many worker processes you're running.&lt;/p&gt;
&lt;h2&gt;
  
  
  Encrypted job arguments
&lt;/h2&gt;

&lt;p&gt;If a job argument is a customer's SSN, an API secret, or anything else you'd&lt;br&gt;
rather not sit in plaintext in Redis or your logs, Enterprise's answer is&lt;br&gt;
transparent AES-256-GCM encryption with key rotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChargeCardJob&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;
  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;encrypt: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The argument is encrypted before it hits Redis and decrypted only inside the&lt;br&gt;
worker process. Wurk implements the same interface.&lt;/p&gt;
&lt;h2&gt;
  
  
  The dashboard, without a Node build
&lt;/h2&gt;

&lt;p&gt;Wurk's Web UI mounts as a Rails engine, and the frontend ships &lt;strong&gt;precompiled&lt;/strong&gt;&lt;br&gt;
inside the gem — installing it doesn't pull Node into your build pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="n"&gt;authenticate&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Wurk&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/wurk"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a live, read-only demo if you want to poke at it before installing&lt;br&gt;
anything: &lt;a href="https://wurk.demo.developerz.ai/wurk" rel="noopener noreferrer"&gt;wurk.demo.developerz.ai&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How this is built
&lt;/h2&gt;

&lt;p&gt;The honest context, because it's relevant if you're deciding whether to trust&lt;br&gt;
this in your stack: wurk is built and maintained largely by AI agents, working&lt;br&gt;
under human review, against a documented parity spec of Sidekiq's public API&lt;br&gt;
(what we call a clean-room implementation — the interface, not the source).&lt;br&gt;
There's a ≥90% line coverage gate enforced in CI, and the parity specs&lt;br&gt;
themselves are written against Sidekiq's own documented OSS/Pro/Enterprise&lt;br&gt;
surface, so behavior drift shows up as a failing test regardless of who wrote&lt;br&gt;
the code.&lt;/p&gt;

&lt;p&gt;It's new one production app running on it internally so far, plus the public&lt;br&gt;
demo. I'm not going to oversell that. If you run Sidekiq at real scale and want&lt;br&gt;
to try wurk against a staging environment, I'd genuinely like to know what breaks.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/developerz-ai/wurk" rel="noopener noreferrer"&gt;https://github.com/developerz-ai/wurk&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://developerz-ai.github.io/wurk/" rel="noopener noreferrer"&gt;https://developerz-ai.github.io/wurk/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What would stop &lt;em&gt;you&lt;/em&gt; from trying this — licensing worry, missing feature,&lt;br&gt;
something else? Curious to hear in the comments.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
