<?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>Forem: Ashwani Yadav</title>
    <description>The latest articles on Forem by Ashwani Yadav (@ashwani_yadav_685e3588953).</description>
    <link>https://forem.com/ashwani_yadav_685e3588953</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810493%2Fc56283ad-6cd8-4e00-87b9-b40cba381a56.png</url>
      <title>Forem: Ashwani Yadav</title>
      <link>https://forem.com/ashwani_yadav_685e3588953</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ashwani_yadav_685e3588953"/>
    <language>en</language>
    <item>
      <title>The Unsung Heroes of Open Source — And the Bot That Has Their Back</title>
      <dc:creator>Ashwani Yadav</dc:creator>
      <pubDate>Sun, 08 Mar 2026 12:08:22 +0000</pubDate>
      <link>https://forem.com/ashwani_yadav_685e3588953/the-unsung-heroes-of-open-source-and-the-bot-that-has-their-back-9en</link>
      <guid>https://forem.com/ashwani_yadav_685e3588953/the-unsung-heroes-of-open-source-and-the-bot-that-has-their-back-9en</guid>
      <description>&lt;h2&gt;
  
  
  A Letter to Every Open Source Maintainer
&lt;/h2&gt;

&lt;p&gt;Before we talk about code, bots, or AI — let's talk about &lt;strong&gt;you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You're at your day job. You have your own targets to hit, your own deadlines to meet, your own features to ship. And somewhere between all of that — you open GitHub to review a stranger's PR.&lt;/p&gt;

&lt;p&gt;Not because someone pays you extra for it. Not because your manager told you to. But because you &lt;strong&gt;care&lt;/strong&gt; about the project and the community around it.&lt;/p&gt;

&lt;p&gt;You review PRs while balancing your own sprint work. You explain the same thing for the 50th time to a new contributor. You close duplicate issues politely between meetings. You do code reviews during your 8-hour workday while still being expected to deliver on your own targets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's not overtime. That's dedication squeezed into an already full day.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And lately, it's gotten harder. The AI era brought an explosion of contributions — some genuine, some... not so much. As &lt;a href="https://dev.to/kaleman15"&gt;Kevin Alemán&lt;/a&gt; beautifully put it in his blog post &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-3482"&gt;&lt;em&gt;"In the AI Era, Code Is Cheap. Reputation Isn't."&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Maintainer capacity did not increase. Review cost did not decrease."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The cost of opening a PR is now near zero. The cost of reviewing it is not."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post is about &lt;strong&gt;one solution&lt;/strong&gt; I discovered — a tiny bot that quietly protects maintainers. And what we, as contributors, can learn from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: More Code, Same Humans
&lt;/h2&gt;

&lt;p&gt;Let's be honest about what's happening in open source right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI makes it easy&lt;/strong&gt; to generate PRs, issues, and even vulnerability reports in bulk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainers are still human&lt;/strong&gt; — they have the same 24 hours, the same energy, the same patience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good contributions get buried&lt;/strong&gt; under a pile of duplicates and low-quality PRs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate work happens&lt;/strong&gt; when 3 people unknowingly work on the same issue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kevin shared something that hit hard — &lt;strong&gt;curl's maintainer had to ban AI-generated vulnerability reports&lt;/strong&gt; because they were drowning in fake ones that &lt;em&gt;looked&lt;/em&gt; legit but wasted hours of manual review time.&lt;/p&gt;

&lt;p&gt;This isn't a problem with AI itself. It's a &lt;strong&gt;workflow problem&lt;/strong&gt;. And one project solved it beautifully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet Zulipbot 🤖
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/zulip/zulip" rel="noopener noreferrer"&gt;Zulip&lt;/a&gt; — the open-source team chat platform — built a small GitHub bot called &lt;strong&gt;&lt;a href="https://github.com/zulip/zulipbot" rel="noopener noreferrer"&gt;zulipbot&lt;/a&gt;&lt;/strong&gt;. It's open source (Apache 2.0), written in Node.js, and does something simple but powerful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It puts structure around contributions.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Issue Claiming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of 3 people racing to PR the same bug, contributors comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@zulipbot claim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot assigns them and marks the issue &lt;code&gt;in progress&lt;/code&gt;. Others can see it's taken. No duplicate work. No wasted effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. One Issue at a Time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the genius part — &lt;strong&gt;you can only claim 1 issue at a time&lt;/strong&gt;. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hoarding 10 issues and delivering nothing&lt;/li&gt;
&lt;li&gt;You finish what you start before taking more&lt;/li&gt;
&lt;li&gt;Maintainers deal with focused, completed work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Automatic Check-ins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Life happens. Sometimes you claim an issue and get busy. After &lt;strong&gt;7 days&lt;/strong&gt; of no activity, the bot gently asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Hey, are you still working on this?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If no response in &lt;strong&gt;3 more days&lt;/strong&gt; — it auto-unassigns, freeing the issue for someone else. No hard feelings. No awkward conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Democratic Labeling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Normally on GitHub, only people with write access can add labels to issues — even the person who &lt;em&gt;created&lt;/em&gt; the issue can't label it. That means maintainers have to manually label everything — more work on their plate.&lt;/p&gt;

&lt;p&gt;Zulipbot changes this. Contributors can add labels to &lt;strong&gt;issues they've opened&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@zulipbot add "bug" "help wanted"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small thing, but it adds up. When contributors can properly categorize their own issues, maintainers spend less time on triage and more time on what actually matters — reviewing code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters — A Maintainer's Perspective
&lt;/h2&gt;

&lt;p&gt;Think about what this bot does from a &lt;strong&gt;maintainer's eyes&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without Bot&lt;/th&gt;
&lt;th&gt;With Bot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Review 3 duplicate PRs for 1 issue&lt;/td&gt;
&lt;td&gt;Review 1 focused PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manually chase inactive contributors&lt;/td&gt;
&lt;td&gt;Bot handles it automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain "someone's already working on this"&lt;/td&gt;
&lt;td&gt;Bot shows &lt;code&gt;in progress&lt;/code&gt; label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get flooded with notifications&lt;/td&gt;
&lt;td&gt;Area-based teams filter alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The bot doesn't replace maintainers. It protects their time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And time is the most precious thing a maintainer has — because they're &lt;strong&gt;volunteering&lt;/strong&gt; it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned as a Contributor
&lt;/h2&gt;

&lt;p&gt;Reading Kevin's blog and studying zulipbot changed how I think about contributing. Here are my takeaways:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Every PR Has a Hidden Cost
&lt;/h3&gt;

&lt;p&gt;Kevin said it perfectly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Every PR has overhead: CI runs, notifications, review time, merge time, mental context switch."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every contributor should ask themselves before opening a PR: &lt;strong&gt;"Am I reducing work for the maintainer, or adding to it?"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Claiming Prevents Chaos
&lt;/h3&gt;

&lt;p&gt;A friend of mine — &lt;a href="https://github.com/VedantGupta-DTU" rel="noopener noreferrer"&gt;Vedant Gupta&lt;/a&gt; — recently started contributing to open source. He told me how he'd see an issue, start coding, and submit a PR — only to find 2 other people had done the exact same thing. That's 3x the review work for maintainers, and 2 contributors whose time was completely wasted.&lt;/p&gt;

&lt;p&gt;A simple claiming system prevents this entirely. When you can see an issue is already claimed, you move on to something else. No wasted effort on either side.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inactivity Tracking Is Kind, Not Harsh
&lt;/h3&gt;

&lt;p&gt;At first, auto-unassign sounds aggressive. But it's actually &lt;strong&gt;the kindest thing&lt;/strong&gt; for everyone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The contributor doesn't feel guilty about dropping something&lt;/li&gt;
&lt;li&gt;The issue doesn't stay stuck for months&lt;/li&gt;
&lt;li&gt;Someone else gets a chance to contribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's better than the awkward silence of a claimed issue with no activity for 6 months.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Structure Empowers New Contributors
&lt;/h3&gt;

&lt;p&gt;When new contributors join Zulip and claim their first issue, the bot sends a &lt;strong&gt;welcome message&lt;/strong&gt; with links to docs, community chat, and guidelines. It feels organized. It feels like the project &lt;em&gt;wants&lt;/em&gt; you there and wants you to succeed.&lt;/p&gt;

&lt;p&gt;Compare that to repos where you open a PR and... silence. For weeks. That silence is what kills contributor motivation.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Proposal: Every Large OSS Project Should Have a Workflow Bot
&lt;/h2&gt;

&lt;p&gt;Here's my honest take — &lt;strong&gt;every open-source project with 100+ contributors should consider a workflow bot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not because contributors are bad. But because &lt;strong&gt;good contributors deserve a structured environment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Zulipbot is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open source&lt;/strong&gt; — &lt;a href="https://github.com/zulip/zulipbot" rel="noopener noreferrer"&gt;github.com/zulip/zulipbot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache 2.0 licensed&lt;/strong&gt; — fork it, adapt it, use it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built on Probot&lt;/strong&gt; — GitHub's official framework for bots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battle-tested&lt;/strong&gt; — running in production across all Zulip repos for years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any project can fork it and adapt it to their needs. The core features — claiming, inactivity tracking, labeling — are universally useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  To the Maintainers Reading This
&lt;/h2&gt;

&lt;p&gt;Thank you.&lt;/p&gt;

&lt;p&gt;Thank you for squeezing PR reviews between your own deadlines. Thank you for explaining things patiently to beginners while your own sprint tasks are piling up. Thank you for keeping the lights on in projects that millions of people depend on.&lt;/p&gt;

&lt;p&gt;You don't get paid extra for this. You don't get promoted for this. Your manager doesn't count it as a deliverable. Most of you don't even get a "thank you" in return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your work matters.&lt;/strong&gt; And if a little bot can give you back even one hour a week — that's worth fighting for.&lt;/p&gt;




&lt;h2&gt;
  
  
  To the Contributors Reading This
&lt;/h2&gt;

&lt;p&gt;Before you open that PR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Did you check if someone's already working on it?&lt;/li&gt;
&lt;li&gt;✅ Did you run tests locally?&lt;/li&gt;
&lt;li&gt;✅ Did you read the contributing guide?&lt;/li&gt;
&lt;li&gt;✅ Is your PR solving ONE clear problem?&lt;/li&gt;
&lt;li&gt;✅ Would you want to review your own PR?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Kevin said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"High quality contributions get reviewed faster. Low signal contributions accumulate."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the AI era, anyone can generate code. But &lt;strong&gt;reputation is built by humans who care&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;a href="https://github.com/zulip/zulipbot" rel="noopener noreferrer"&gt;Zulipbot Source Code&lt;/a&gt; — Fork it, study it, use it&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://zulip.readthedocs.io/en/latest/contributing/zulipbot-usage.html" rel="noopener noreferrer"&gt;Zulipbot Usage Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔥 Kevin's blog: &lt;a href="https://dev.to/kaleman15/in-the-ai-era-code-is-cheap-reputation-isnt-5fho"&gt;In the AI Era, Code Is Cheap. Reputation Isn't.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💀 &lt;a href="https://daniel.haxx.se/blog/2024/01/02/the-i-in-llm-stands-for-intelligence/" rel="noopener noreferrer"&gt;curl's AI slop ban&lt;/a&gt; — Why curl bans AI-generated vulnerability reports&lt;/li&gt;
&lt;li&gt;🌟 &lt;a href="https://zulip.readthedocs.io/en/latest/contributing/contributing.html" rel="noopener noreferrer"&gt;Zulip Contributing Guide&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If this helped you, share it with your open-source community. And next time you see a maintainer online — say thank you. They deserve it. ❤️&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Connect with me: &lt;a href="https://github.com/NAME-ASHWANIYADAV" rel="noopener noreferrer"&gt;@NAME-ASHWANIYADAV&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>How Background Jobs Actually Work in Rocket.Chat — A Deep Dive into Agenda</title>
      <dc:creator>Ashwani Yadav</dc:creator>
      <pubDate>Sat, 07 Mar 2026 14:04:25 +0000</pubDate>
      <link>https://forem.com/ashwani_yadav_685e3588953/how-background-jobs-actually-work-in-rocketchat-a-deep-dive-into-agenda-858</link>
      <guid>https://forem.com/ashwani_yadav_685e3588953/how-background-jobs-actually-work-in-rocketchat-a-deep-dive-into-agenda-858</guid>
      <description>&lt;p&gt;You know that feeling when you look at an open source codebase for the first time and think, "Wow, this is massive, where do I even start?"&lt;/p&gt;

&lt;p&gt;That's exactly how I felt when I started exploring &lt;a href="https://github.com/RocketChat/Rocket.Chat" rel="noopener noreferrer"&gt;Rocket.Chat&lt;/a&gt;. I was curious about one specific thing: &lt;strong&gt;how does Rocket.Chat handle background tasks?&lt;/strong&gt; Things like cleaning up old files, syncing users from LDAP, sending scheduled reports — all the stuff that happens behind the scenes while you're busy chatting.&lt;/p&gt;

&lt;p&gt;Turns out, the answer is a library called &lt;strong&gt;Agenda&lt;/strong&gt;, and how Rocket.Chat uses it taught me a lot more than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use &lt;code&gt;setInterval&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;This was my first question. Node.js already has &lt;code&gt;setInterval&lt;/code&gt;. Why bring in a whole library?&lt;/p&gt;

&lt;p&gt;Think about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server crashes?&lt;/strong&gt; Your &lt;code&gt;setInterval&lt;/code&gt; is gone. The job never runs again until someone manually restarts things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running multiple instances?&lt;/strong&gt; Great, now the same job runs 3 times simultaneously on 3 different servers. That's not a feature, that's a bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No history?&lt;/strong&gt; Good luck figuring out &lt;em&gt;when&lt;/em&gt; something last ran, or &lt;em&gt;why&lt;/em&gt; it failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agenda solves all of these by storing jobs in &lt;strong&gt;MongoDB&lt;/strong&gt;. The database becomes the single source of truth. If a server goes down, another one picks up the job. If two servers try to grab the same job, MongoDB's atomic operations ensure only one wins.&lt;/p&gt;

&lt;p&gt;It's basically &lt;code&gt;setInterval&lt;/code&gt; that survived the chaos of distributed systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rocket.Chat doesn't use Agenda directly
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't expect: Rocket.Chat maintains its own &lt;strong&gt;forked version&lt;/strong&gt; of Agenda. Check &lt;a&gt;packages/agenda/package.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"description": "Fork of https://github.com/agenda/agenda"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fork lives locally in the monorepo, which means Rocket.Chat has full control over the scheduling behavior without depending on upstream releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Layers
&lt;/h2&gt;

&lt;p&gt;After reading through the code, I noticed Agenda is used in three distinct layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: The Engine (&lt;code&gt;packages/agenda&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is the raw scheduling engine. The core &lt;code&gt;Agenda&lt;/code&gt; class in &lt;code&gt;Agenda.ts&lt;/code&gt; handles everything: database connections, job definitions, polling MongoDB every minute to find due jobs, locking them with atomic &lt;code&gt;findOneAndUpdate&lt;/code&gt;, executing them, and managing concurrency limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: The Wrapper (&lt;code&gt;packages/cron&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Rocket.Chat built a wrapper called &lt;code&gt;AgendaCronJobs&lt;/code&gt; on top of the engine. It does two key things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Simplifies the API:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cronJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VideoConferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 */3 * * *&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expireOldConferences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Records execution history:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time a job runs, the wrapper logs it to &lt;code&gt;rocketchat_cron_history&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;insertedId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CronHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;intendedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// job runs...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CronHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;insertedId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;finishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is invaluable for debugging — you can see exactly when a job ran, how long it took, and whether it failed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: The Actual Jobs
&lt;/h3&gt;

&lt;p&gt;These are spread across the codebase. They don't care about MongoDB or locking. They just say "run this function on this schedule" and the layers below handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good way to think about it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 1 = the postal system (handles delivery logistics)&lt;/li&gt;
&lt;li&gt;Layer 2 = the post office (accepts your letter and tracks it)&lt;/li&gt;
&lt;li&gt;Layer 3 = you writing the letter (just defines the content)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;IJob&lt;/code&gt; Interface — What Gets Stored
&lt;/h2&gt;

&lt;p&gt;Reading &lt;code&gt;packages/agenda/src/definition/IJob.ts&lt;/code&gt; was eye-opening. Every job in MongoDB has these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IJob&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// Job identifier&lt;/span&gt;
  &lt;span class="nl"&gt;nextRunAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// When it should run next&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;once&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;single&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;repeatInterval&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Cron expression&lt;/span&gt;
  &lt;span class="nl"&gt;lastRunAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// When it last started&lt;/span&gt;
  &lt;span class="nl"&gt;lastFinishedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// When it last completed&lt;/span&gt;
  &lt;span class="nl"&gt;lockedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Lock timestamp&lt;/span&gt;
  &lt;span class="nl"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Can disable without deleting!&lt;/span&gt;
  &lt;span class="nl"&gt;failedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// When it failed&lt;/span&gt;
  &lt;span class="nl"&gt;failReason&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// Error message&lt;/span&gt;
  &lt;span class="nl"&gt;failCount&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Cumulative failure count&lt;/span&gt;
  &lt;span class="nl"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// Execution priority&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/strong&gt;: There's already built-in support for disabling jobs without removing them. The engine checks &lt;code&gt;disabled: { $ne: true }&lt;/code&gt; when finding the next job to run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lockedAt&lt;/code&gt;&lt;/strong&gt;: This is how Agenda prevents duplicate execution across servers. Once a server locks a job, others skip it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;failCount&lt;/code&gt; + &lt;code&gt;failReason&lt;/code&gt;&lt;/strong&gt;: Error tracking is built into the data model. You don't need external monitoring to know how often something fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Job Class Has More Than I Expected
&lt;/h2&gt;

&lt;p&gt;Looking at &lt;code&gt;Job.ts&lt;/code&gt;, I found methods I wasn't expecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Built-in disable/enable&lt;/span&gt;
&lt;span class="nf"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Status check&lt;/span&gt;
&lt;span class="nf"&gt;isRunning&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastRunAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastFinishedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lockedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastRunAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastFinishedAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the infrastructure for checking job status and toggling jobs on/off already exists at the engine level. That's pretty cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Job Actually Runs — End to End
&lt;/h2&gt;

&lt;p&gt;Following a job from schedule to execution was one of the more interesting exercises:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Registration&lt;/strong&gt; — &lt;code&gt;cronJobs.add()&lt;/code&gt; calls &lt;code&gt;agenda.define()&lt;/code&gt; to register the handler, then &lt;code&gt;agenda.every()&lt;/code&gt; to set the schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Polling&lt;/strong&gt; — Every minute, Agenda queries MongoDB for jobs where &lt;code&gt;nextRunAt &amp;lt;= now&lt;/code&gt; AND &lt;code&gt;disabled !== true&lt;/code&gt; AND &lt;code&gt;lockedAt&lt;/code&gt; is null or expired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Locking&lt;/strong&gt; — &lt;code&gt;findOneAndUpdate&lt;/code&gt; atomically sets &lt;code&gt;lockedAt = new Date()&lt;/code&gt;. Because it's atomic, even if two servers query simultaneously, only one gets the lock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Execution&lt;/strong&gt; — The handler runs. Events fire: &lt;code&gt;start&lt;/code&gt; → &lt;code&gt;success&lt;/code&gt;/&lt;code&gt;fail&lt;/code&gt; → &lt;code&gt;complete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Cleanup&lt;/strong&gt; — &lt;code&gt;lockedAt&lt;/code&gt; is set to &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;nextRunAt&lt;/code&gt; is recalculated from the cron expression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Lock Expiry&lt;/strong&gt; — Default lock lifetime is 10 minutes. If a server crashes while holding a lock, after 10 minutes another server can reclaim the job. Self-healing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jobs Are Everywhere (Not Just &lt;code&gt;server/cron/&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;This was my biggest surprise. I assumed all background jobs lived in &lt;code&gt;apps/meteor/server/cron/&lt;/code&gt;. That folder has 6 files — NPS surveys, OEmbed cache cleanup, video conference expiry, etc.&lt;/p&gt;

&lt;p&gt;But when I searched the entire codebase for &lt;code&gt;cronJobs.add(&lt;/code&gt;, I found &lt;strong&gt;19 distinct jobs&lt;/strong&gt; registered across many different modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core&lt;/strong&gt; (6): NPS, OEmbed cleanup, usage reports, video conferences, temp file cleanup, user data exports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System&lt;/strong&gt; (4): Version checking, cloud workspace sync, retention policy pruning, Smarsh compliance exports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; (5): CROWD sync, LDAP sync (with 4 sub-jobs: user sync, avatar sync, auto-logout, attribute-based access control)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt; (2): Marketplace update checks, app request notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Livechat&lt;/strong&gt; (2): Business hour scheduling, daylight saving time adjustment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each module has its own patterns too. Some jobs have &lt;strong&gt;fixed schedules&lt;/strong&gt; (run at 2 AM daily), some are &lt;strong&gt;setting-driven&lt;/strong&gt; (admin changes frequency from the UI), and some use &lt;strong&gt;random offsets&lt;/strong&gt; — like &lt;code&gt;Cloud Workspace Sync&lt;/code&gt; which picks a random minute to prevent all Rocket.Chat instances from hitting the cloud API at the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cronJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;licenseCronName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; */12 * * *`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  There Are Actually Two Scheduling Systems
&lt;/h2&gt;

&lt;p&gt;Here's something that took me a while to piece together. Rocket.Chat doesn't have just one scheduling system — it has &lt;strong&gt;two&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Core Cron Jobs&lt;/strong&gt; → Use the &lt;code&gt;AgendaCronJobs&lt;/code&gt; wrapper → Store jobs in &lt;code&gt;rocketchat_cron&lt;/code&gt; → History goes to &lt;code&gt;rocketchat_cron_history&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Apps Engine Scheduler&lt;/strong&gt; → Uses its own separate Agenda instance via &lt;code&gt;AppSchedulerBridge&lt;/code&gt; → Stores jobs in &lt;code&gt;rocketchat_apps_scheduler&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Apps Engine (Rocket.Chat's app framework) has a completely independent scheduler. When a marketplace app registers background tasks, those go into a different MongoDB collection entirely. Different Agenda instance, different collection, different lifecycle.&lt;/p&gt;

&lt;p&gt;This means if you ever wanted a complete picture of all background tasks running in a Rocket.Chat instance, you'd need to look at &lt;strong&gt;three separate collections&lt;/strong&gt;, not two.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Read the code, not just the docs.&lt;/strong&gt;&lt;br&gt;
I understood locking by &lt;em&gt;reading&lt;/em&gt; &lt;code&gt;_findAndLockNextJob()&lt;/code&gt;, not by reading about it. Documentation gives you the "what." Source code gives you the "how" and "why."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;grep&lt;/code&gt; is your best friend.&lt;/strong&gt;&lt;br&gt;
I only found 19 jobs because I searched the entire codebase for &lt;code&gt;cronJobs.add(&lt;/code&gt; instead of just browsing one folder. Assumptions about where code lives will mislead you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Layered architecture makes complexity manageable.&lt;/strong&gt;&lt;br&gt;
The person writing an NPS job doesn't need to know about MongoDB locking. The layers abstract that away beautifully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Background jobs are invisible infrastructure.&lt;/strong&gt;&lt;br&gt;
Users never see them, but without them, files don't get cleaned up, licenses don't sync, old messages don't get pruned, and LDAP users fall out of date. They're the unsung heroes of the application.&lt;/p&gt;




&lt;p&gt;If you're exploring a large open source codebase, I'd recommend picking one system and following it end-to-end. Don't try to understand everything at once — just pick a thread and pull it.&lt;/p&gt;

&lt;p&gt;Happy exploring! 🚀&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
