<?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: Sai Earanti</title>
    <description>The latest articles on DEV Community by Sai Earanti (@thesaiearanti).</description>
    <link>https://dev.to/thesaiearanti</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%2F4214158%2F0ac627f6-6fe8-4eea-a1fb-1f4445fd2cc5.png</url>
      <title>DEV Community: Sai Earanti</title>
      <link>https://dev.to/thesaiearanti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thesaiearanti"/>
    <language>en</language>
    <item>
      <title>My Hydra fix passed every test. A few lines of YAML proved it wrong.</title>
      <dc:creator>Sai Earanti</dc:creator>
      <pubDate>Thu, 29 Oct 2026 02:52:53 +0000</pubDate>
      <link>https://dev.to/thesaiearanti/my-hydra-fix-passed-every-test-a-few-lines-of-yaml-proved-it-wrong-3j72</link>
      <guid>https://dev.to/thesaiearanti/my-hydra-fix-passed-every-test-a-few-lines-of-yaml-proved-it-wrong-3j72</guid>
      <description>&lt;p&gt;So this summer I fixed a bug in Hydra. If you haven't used it, Hydra is the config framework that came out of Meta, and a lot of ML projects use it to manage their configs.&lt;/p&gt;

&lt;p&gt;My fix passed every test. I was pretty happy with it. Then the maintainer replied with four lines of YAML that broke it.&lt;/p&gt;

&lt;p&gt;Honestly, that review taught me more than the fix did, so here's the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick background: the Defaults List
&lt;/h2&gt;

&lt;p&gt;In Hydra, a config can start with a &lt;code&gt;defaults&lt;/code&gt; list. That's where you say which options to pull in, like "use &lt;code&gt;file1&lt;/code&gt; for &lt;code&gt;group1&lt;/code&gt;." You can also write &lt;code&gt;override group1: file3&lt;/code&gt;, which swaps in a different option for that group.&lt;/p&gt;

&lt;p&gt;So what happens when two overrides fight over the same group? The docs have a rule for that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a Group Default is overridden more than once, the last one, in depth first order, wins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Depth first just means Hydra walks the whole tree. If something in your list has its own defaults list, Hydra goes all the way into it before it moves on to the next entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;The maintainer, omry, had filed this one as #3229. Say your main config overrides &lt;code&gt;group1&lt;/code&gt; to &lt;code&gt;b&lt;/code&gt;. Then you add an experiment config from the command line with &lt;code&gt;+experiment=...&lt;/code&gt;, and that experiment overrides &lt;code&gt;group1&lt;/code&gt; to &lt;code&gt;c&lt;/code&gt;. The experiment comes later, so &lt;code&gt;c&lt;/code&gt; should win. You got &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once I found it, the cause was pretty simple. Hydra keeps a record of overrides, and &lt;code&gt;add_override&lt;/code&gt; only kept the first one it saw for each group. So the first override won, not the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first fix
&lt;/h2&gt;

&lt;p&gt;My idea was to register all of a config's overrides up front, before Hydra walks into that config's children. If a later override showed up for the same group, it could replace the earlier one, unless the earlier one had already been used.&lt;/p&gt;

&lt;p&gt;It fixed the bug from the issue. All the existing tests passed. I opened the PR feeling good about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four lines of YAML
&lt;/h2&gt;

&lt;p&gt;A few hours later, omry replied with this:&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;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;experiment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;override_config_group&lt;/span&gt;  &lt;span class="c1"&gt;# overrides group1 with file2&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;override group1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;experiment&lt;/code&gt; config has its own override that sets &lt;code&gt;group1&lt;/code&gt; to &lt;code&gt;file2&lt;/code&gt;. Then the last line of the main config overrides it to &lt;code&gt;file3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Walk it depth first. You go into &lt;code&gt;experiment&lt;/code&gt; and hit &lt;code&gt;file2&lt;/code&gt;. Then you come back out and hit &lt;code&gt;file3&lt;/code&gt;. So &lt;code&gt;file3&lt;/code&gt; is last, and it should win.&lt;/p&gt;

&lt;p&gt;My patch gave &lt;code&gt;file2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's why. I registered the main config's overrides before going into any of its children, so &lt;code&gt;file3&lt;/code&gt; got registered early. Then &lt;code&gt;file2&lt;/code&gt; showed up later, while Hydra was inside &lt;code&gt;experiment&lt;/code&gt;, and my replace rule let it take over. I was going by when things got registered, and that is just not the same thing as depth-first order. The "already used" flag was standing in for the real rule, and it only happened to work for the case in the issue.&lt;/p&gt;

&lt;p&gt;omry summed it up in one line: "The &lt;code&gt;used&lt;/code&gt; flag reflects the current traversal state, not the overrides' depth-first ordering."&lt;/p&gt;

&lt;p&gt;None of the existing tests had that ordering, so nothing caught it. Tests only cover the cases somebody already thought of.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real fix was smaller
&lt;/h2&gt;

&lt;p&gt;The review also explained how to fix it, and honestly it was really elegant. Hydra already walks each defaults list in reverse. So if you register each override at the moment you reach it in that reverse walk, the first one you register for a group is the last one in depth-first order. And &lt;code&gt;add_override&lt;/code&gt; already kept the first one it saw.&lt;/p&gt;

&lt;p&gt;So that first-wins behavior was right all along. The bug was where the call happened, not what it did.&lt;/p&gt;

&lt;p&gt;I moved the registration into the reverse traversal and left &lt;code&gt;add_override&lt;/code&gt; the way it was. Command-line overrides get registered before any of this happens, so they still beat everything in the defaults lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The edge case I almost buried
&lt;/h2&gt;

&lt;p&gt;While I was testing the new version, I found one case that broke:&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="c1"&gt;# primary config&lt;/span&gt;
&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;override db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python my_app.py +db&lt;span class="o"&gt;=&lt;/span&gt;mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On main, that gives you &lt;code&gt;sqlite&lt;/code&gt;. With my new code it failed with &lt;code&gt;Could not override 'db'&lt;/code&gt;, because the reverse walk reaches the appended &lt;code&gt;db&lt;/code&gt; entry before the override gets registered.&lt;/p&gt;

&lt;p&gt;I could have quietly patched around it. Instead I handled it narrowly, so the behavior stayed exactly like main, and I called it out in the PR as a spot where I'd gone beyond what was asked.&lt;/p&gt;

&lt;p&gt;omry's answer: "Your analysis of Hydra's current behavior is correct, but that behavior is a separate bug." That became its own issue, #3318, and I fixed it the next day in #3319.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests
&lt;/h2&gt;

&lt;p&gt;I ended up with regression tests where each one fails on exactly one wrong version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the ordering from the original issue fails on main&lt;/li&gt;
&lt;li&gt;omry's four lines fail on my first patch&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;+db=mysql&lt;/code&gt; case fails without the special handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's my favorite part of the whole PR. If every test kills one specific wrong implementation, you know your tests are actually doing something.&lt;/p&gt;

&lt;p&gt;The PR got opened, reviewed, reworked and merged all on the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Passing tests isn't the same as being right.&lt;/strong&gt; Tests only cover what someone already thought of. Now, for anything where order matters, I try to break my own fix by shuffling the order before I ask anyone to review it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code the actual rule.&lt;/strong&gt; I wrote code for a stand-in (when things got registered, plus a flag) instead of the rule itself (depth-first position). They agreed on the easy case and disagreed on the interesting one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag the weird stuff.&lt;/strong&gt; The edge case I called out turned into its own issue and my next merged PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take the hard review.&lt;/strong&gt; omry even mentioned that if this turned out to be more than I wanted to take on, there were easier issues in the 1.4 milestone. I'm really glad I stuck with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to dig in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The PR: &lt;a href="https://github.com/hydra-ecosystem/hydra/pull/3315" rel="noopener noreferrer"&gt;hydra#3315&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The original bug: &lt;a href="https://github.com/hydra-ecosystem/hydra/issues/3229" rel="noopener noreferrer"&gt;hydra#3229&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The follow-up: &lt;a href="https://github.com/hydra-ecosystem/hydra/issues/3318" rel="noopener noreferrer"&gt;hydra#3318&lt;/a&gt;, fixed in &lt;a href="https://github.com/hydra-ecosystem/hydra/pull/3319" rel="noopener noreferrer"&gt;hydra#3319&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>debugging</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
