code.dblock.org | tech blogJekyll2026-03-12T21:26:26+00:00https://code.dblock.org/Daniel Doubrovkinehttps://code.dblock.org/[email protected]https://code.dblock.org/2026/03/12/ai-slop-a-slack-api-rate-limiting-disaster2026-03-12T00:00:00+00:002026-03-12T00:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Yesterday I <a href="https://code.dblock.org/2026/03/11/right-sizing-engineering-teams-for-ai.html">described</a> AI-generated code as “plausible-looking, locally coherent, globally wrong.” Here’s a concrete example from my own codebase.</p>
<p>I needed a cleanup job to close old Slack group DM conversations in my <a href="https://github.com/dblock/slack-sup2">slack-sup2</a> app. The AI-generated solution looked perfectly reasonable:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">close_old_sups!</span>
<span class="k">return</span> <span class="mi">0</span> <span class="k">unless</span> <span class="n">sup_close</span>
<span class="n">old_sups</span> <span class="o">=</span> <span class="n">sups</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="n">conditions</span><span class="p">)</span>
<span class="n">old_sups</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&</span><span class="ss">:close!</span><span class="p">)</span>
<span class="n">old_sups</span><span class="p">.</span><span class="nf">count</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">close!</span>
<span class="k">return</span> <span class="k">unless</span> <span class="n">conversation_id</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">closed_at</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">info</span> <span class="s2">"Closing DM channel </span><span class="si">#{</span><span class="n">conversation_id</span><span class="si">}</span><span class="s2">..."</span>
<span class="n">slack_client</span><span class="p">.</span><span class="nf">conversations_close</span><span class="p">(</span><span class="ss">channel: </span><span class="n">conversation_id</span><span class="p">)</span>
<span class="n">update_attributes!</span><span class="p">(</span><span class="ss">closed_at: </span><span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">utc</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This code looks pretty great and completely breaks the app. Slack’s API has a <a href="https://api.slack.com/docs/rate-limits">global rate limit of 1 request per second</a> for the <code class="language-plaintext highlighter-rouge">conversations.close</code> endpoint. So, when this job runs against a workspace with hundreds of old conversations, it immediately hits the rate limit. Worse, because rate limits are global across all endpoints, it takes down the entire application. Every other API call — posting messages, fetching user info, everything — starts failing.</p>
<p>I asked AI to address this. It made the fix catastrophically worse.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">close!</span>
<span class="c1"># ... existing code ...</span>
<span class="k">begin</span>
<span class="n">slack_client</span><span class="p">.</span><span class="nf">conversations_close</span><span class="p">(</span><span class="ss">channel: </span><span class="n">conversation_id</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Slack</span><span class="o">::</span><span class="no">Web</span><span class="o">::</span><span class="no">Api</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">TooManyRequests</span> <span class="o">=></span> <span class="n">e</span>
<span class="n">sleep_time</span> <span class="o">=</span> <span class="n">e</span><span class="p">.</span><span class="nf">retry_after</span> <span class="o">||</span> <span class="mi">60</span>
<span class="nb">sleep</span><span class="p">(</span><span class="n">sleep_time</span><span class="p">)</span>
<span class="k">retry</span>
<span class="k">end</span>
<span class="c1"># ... rest of method ...</span>
<span class="k">end</span>
</code></pre></div></div>
<p>In <a href="https://github.com/socketry/async">socketry/async</a>, <code class="language-plaintext highlighter-rouge">sleep()</code> blocks the entire fiber and prevents other concurrent operations from executing. You should use <code class="language-plaintext highlighter-rouge">Async::Task.sleep()</code> instead, but that still doesn’t solve the fundamental architectural problem of making hundreds of sequential API calls.</p>
<p>What made this particularly insidious is that the assistant wrote some pretty professional-looking code and seemingly handled obvious edge cases. Yet, it failed to consider the distributed system constraints or global invariants. The rate limiting problem wasn’t visible in the local scope of the method - it was a system-wide concern that required human judgment.</p>
<p>So, how did I actually fix the problem? I used the initial A.I. implementation (<a href="https://github.com/dblock/slack-sup2/pull/93/changes/4c11b3a01e795d23ae6742fc868229ee4d32a9af">4c11b3a0</a>), but moved the task into the 30 minute cron to avoid triggering global rate limits (<a href="https://github.com/dblock/slack-sup2/pull/93/changes/ab9e9518c1992fe76d7c8563a318a699bd84eddc">ab9e9518</a>). I then put the feature behind a setting that is off by default and wrote a script to slow-drain the many thousands of unclosed DM channels for existing customers (<a href="https://github.com/dblock/slack-sup2/pull/93/changes/9feabd2c8fdea79e8e02e6e38c19b9b6517e76ee">9feabd2c</a>). Finally, I … ahem … Copilot refactored the code to auto-close a limited number of DMs at any given time to avoid the rate limit altogether (<a href="https://github.com/dblock/slack-sup2/pull/94">#94</a>).</p>
<p>For now, human oversight of A.I. assistants remains critical.</p>
<p><a href="https://code.dblock.org/2026/03/12/ai-slop-a-slack-api-rate-limiting-disaster.html">AI Slop: A Slack API Rate Limiting Disaster</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on March 12, 2026.</p>https://code.dblock.org/2026/03/11/right-sizing-engineering-teams-for-ai2026-03-11T00:00:00+00:002026-03-11T00:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Before AI coding assistants, a typical engineering team of 8-10 people might have been lucky to have one or two “10x engineers”, or “workhorses”, the kind of engineer that both keeps project quality and feature velocity high.</p>
<p>AI tools have solved the workhorse half of this equation, enabling massive raw output. Today, almost every engineer can produce a high volume of code with GitHub Copilot, Claude, or Cursor.</p>
<p>But the quality half of the equation has not kept up. Teams are shipping more code, but a greater fraction of it is AI slop: plausible-looking, locally coherent, globally wrong. Code review remains a human activity. Until that changes, AI assistants cannot substitute for the senior engineer. If AI triples output but the number of senior reviewers stays the same, the ratio of experienced judgment to code produced has gotten roughly 3x worse.</p>
<p>The instinctive response to higher individual productivity is to hire fewer people, which is correct directionally but wrong in practice if you cut experience rather than volume. The difference is not in lines of code produced; it is in the accumulated judgment applied at every decision point. A team of four or five senior engineers with AI assistants will, in my experience, outperform a team of ten mixed-experience engineers with the same tools because the ratio of judgment to output stays healthy.</p>
<p>This has implications for how engineering leaders should think about headcount planning. A well-functioning engineering team today should be five to seven people, with at most one junior. A reliable signal that you’ve gotten this wrong: pull requests that sit unreviewed for days, not because people are busy, but because no one feels confident enough to approve them.</p>
<p>Smaller, more experienced engineering teams are not a new idea - Fred Brooks noted that you cannot make a late project earlier by adding people. What AI has done is make the argument sharper and more urgent. When every engineer can produce the volume that once required three, the scarcest resource is no longer effort; it is experience. Optimize for that.</p>
<p><a href="https://code.dblock.org/2026/03/11/right-sizing-engineering-teams-for-ai.html">Right-Sizing Engineering Teams for AI</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on March 11, 2026.</p>https://code.dblock.org/2026/02/04/the-golden-ratio-of-manager-to-ic2026-02-04T00:00:00+00:002026-02-04T00:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>In <a href="https://www.wsj.com/tech/ai/meta-to-create-new-applied-ai-engineering-organization-in-reality-labs-division-d41c4a69">today’s shocker</a>, Meta is to “create a new applied AI engineering organization aiming for an ultra-flat structure of up to 50 employees to one manager”.</p>
<p><img src="/images/posts/2026/2026-02-04-the-golden-ratio-of-manager-to-ic/meta-applied-ai-engineering.png" alt="Meta to create new applied AI engineering organization with ultra-flat structure" /></p>
<p>Like all software engineers I, too, tend to apply a data‑driven, mathematical approach to every problem in the world. Yet I would have chosen a more romantic number and applied the golden ratio: roughly 1.6:1, the proportion that shows up in seashells, galaxies, and every second slide about “natural elegance”, rather than 50:1, a measure that feels less like harmony and more like a spreadsheet’s idea of efficiency.</p>
<p>The idea of flattening an organization is not new and can be a good one. I know plenty of managers who have not done any individual contributor work, code or otherwise, in years. This is particularly striking with former strong coders who are promoted to managerial roles. After 2–3 cycles of promotions they are so far detached from what’s happening at the individual‑contributor level that they become 100% overhead, spending their entire life in meetings and actively preventing real work from being done. It’s natural to want to eliminate layers of such people as they simply don’t have any impact. And so, the real news at Meta is that it’s fighting its own organization design in which, at least in some teams according to my friends who work or have worked there, people managers are discouraged from doing deep technical work, don’t own much beyond process, and mostly serve as reporting‑structure placeholders.</p>
<p>Another reason to flatten an organization is the introduction of AI assistants that have created a major shift in the capabilities of individual contributors. Two years ago you could maybe find one single “10x engineer” in every team—someone who has dramatically higher velocity than their peers. A good manager would recognize these extraordinary abilities, make such an individual their right hand and technical partner, share the responsibility of advancing a project, create effective mentorship, and help bring the rest of the team along, distributing work in ways that optimize for the long term where juniors are grown slowly and incrementally. But with AI every engineer can—and in fact must—become a 10x engineer, fast. It therefore makes sense for a flagship AI team to hire already experienced, top‑1% engineers and not bother with juniors, thus requiring fewer managers.</p>
<p>The role of the manager must continue to evolve, but it has fundamentally not changed in my opinion. It remains critically important for any level of manager to be a role model and to deliver results. In a healthy team a manager has time to do some individual‑contributor work, and that is my favorite kind of manager. Today, this could mean using AI assistants to write some code. Beyond that, a manager’s job is to take ownership, articulate a vision and mission, disambiguate and provide clarity to their team, work with customers, recognize and show people what they are capable of, enable human aspirations, and take responsibility for shortcomings. If a manager does “real work”, it matters less how many people report to them; we want a team sized to the problem we are trying to solve, to create the right kind of focus, rather than to enforce a ratio.</p>
<p>An organization is just a tool to delineate responsibilities and provide clarity of purpose to groups of people. When we begin adjusting ratios, we forget that we are working with humans, and that like all metrics, the number is not the goal.</p>
<p><a href="https://code.dblock.org/2026/02/04/the-golden-ratio-of-manager-to-ic.html">The Golden Ratio of Manager to IC</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on February 04, 2026.</p>https://code.dblock.org/2026/01/15/serving-markdown-for-ai-agents2026-01-15T00:00:00+00:002026-01-15T00:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Dries Buytaert recently wrote about <a href="https://dri.es/the-third-audience">The Third Audience</a>. For decades, websites have targeted two audiences: humans and search engines. AI agents are now the third audience, and most websites aren’t optimized for them yet.</p>
<p>AI agents prefer clean, structured content over HTML. Markdown is ideal - it’s readable, semantic, and free of navigation chrome. So I made this blog serve its source markdown files alongside the HTML.</p>
<h3 id="how-it-works">How It Works</h3>
<p>For every post like <a href="/2026/01/15/serving-markdown-for-ai-agents.html">/2026/01/15/serving-markdown-for-ai-agents.html</a>, you can now fetch the source at <a href="/2026/01/15/serving-markdown-for-ai-agents.md">/2026/01/15/serving-markdown-for-ai-agents.md</a>.</p>
<p>AI agents can discover this via a <code class="language-plaintext highlighter-rouge"><link></code> tag in the HTML head:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><link</span> <span class="na">href=</span><span class="s">"serving-markdown-for-ai-agents.md"</span>
<span class="na">type=</span><span class="s">"text/markdown"</span>
<span class="na">rel=</span><span class="s">"alternate"</span>
<span class="na">title=</span><span class="s">"Markdown"</span><span class="nt">></span>
</code></pre></div></div>
<h3 id="implementation">Implementation</h3>
<p>Since this is a GitHub Pages blog, custom Jekyll plugins don’t work. Instead, I added a <a href="https://github.com/dblock/code.dblock.org/blob/gh-pages/.github/workflows/deploy.yml">GitHub Actions workflow</a> that builds Jekyll and copies the markdown source files to <code class="language-plaintext highlighter-rouge">_site</code> before deployment.</p>
<p>The workflow extracts the date and slug from each post filename and copies it to the matching URL path with a <code class="language-plaintext highlighter-rouge">.md</code> extension.</p>
<h3 id="should-you-do-this">Should You Do This?</h3>
<p>Dries raises a valid concern: are we just making it easier for AI companies to use our content without sending traffic back? Perhaps. But AI agents are already crawling our sites. Giving them cleaner input might lead to better attribution and more accurate responses that reference our work.</p>
<p>The web has always been about making information accessible. This is just the next evolution.</p>
<p>Plus, we will soon all work for AI anyway, so might as well get on its good side.</p>
<h3 id="meta">Meta</h3>
<p>This entire feature was implemented by <a href="https://claude.ai">Claude</a>, and this post was written by it as well.</p>
<ul>
<li><a href="https://github.com/dblock/code.dblock.org/commit/6fab9f3">Added GitHub Actions workflow to deploy and serve markdown.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/45769b0">Use Ruby 3.4 in deploy workflow.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/e5cb737">Also serve page markdown files.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/fcff077">Added link rel alternate for markdown discovery.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/e5eede2">Moved markdown link to post layout.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/f6ae02a">Fixed markdown alternate URL to remove .html.</a></li>
<li><a href="https://github.com/dblock/code.dblock.org/commit/5ef474c">Serving Markdown for AI Agents.</a></li>
</ul>
<p><a href="https://code.dblock.org/2026/01/15/serving-markdown-for-ai-agents.html">Serving Markdown for AI Agents in Jekyll</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on January 15, 2026.</p>https://code.dblock.org/2025/09/18/turning-hypermedia-apis-into-mcps2025-09-18T09:00:00+00:002025-09-18T09:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>I’ve <a href="https://code.dblock.org/2014/07/18/serving-hypermedia-with-a-grape-api-and-roar.html">written</a> and <a href="https://www.slideshare.net/slideshow/designing-a-hypermedia-api-with-grape-roar/41151492">talked</a> extensively about Hypermedia APIs for about a decade. The <a href="https://en.wikipedia.org/wiki/HATEOAS">HATEOAS</a> constraints in representing resources for a RESTful API has numerous advantages in API design, readability, discoverability, and performance over a hand-rolled REST API. Unfortunately, adoption of Hypermedia APIs is very low compared to, for example GraphQL, but I find the implementation a lot simpler and continue adding Hypermedia APIs to my pet projects.</p>
<p>At its root a Hypermedia API looks like so.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"_links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sup2.playplay.io/api"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sup2.playplay.io/api/status"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"team"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sup2.playplay.io/api/teams/{id}"</span><span class="p">,</span><span class="w">
</span><span class="nl">"templated"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The above example is an extract from <a href="https://sup2.playplay.io/api">this API</a>. For this post we will use <a href="https://sup2.playplay.io/">one of my Slack bots called S’Up</a>, which generates fresh triads of team members in Slack to meet for coffee every week in an informal standup. The bot is <a href="https://github.com/dblock/slack-sup2">open-source</a> and you can try it <a href="https://sup2.playplay.io/">here</a>.</p>
<p>Because the Hypermedia API structure is fixed with only “resources” and “links” (and similar to API frameworks such as GraphQL) you don’t need an application-specific client to interact with the API. In Ruby, the most popular generic Hypermedia client is <a href="https://github.com/codegram/hyperclient">Hyperclient</a>.</p>
<p>With the API above, a client can retrieve the bot status, and using an API token obtained from an existing installation some team information.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">api</span> <span class="o">=</span> <span class="no">Hyperclient</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'https://sup2.playplay.io/api'</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">client</span><span class="o">|</span>
<span class="n">client</span><span class="p">.</span><span class="nf">headers</span><span class="p">[</span><span class="s1">'X-Access-Token'</span><span class="p">]</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'TOKEN'</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">status</span> <span class="o">=</span> <span class="n">api</span><span class="p">.</span><span class="nf">status</span>
<span class="nb">puts</span> <span class="s2">"Bot is </span><span class="si">#{</span><span class="n">status</span><span class="p">.</span><span class="nf">ping</span><span class="p">[</span><span class="s1">'presence'</span><span class="p">][</span><span class="s1">'presence'</span><span class="p">]</span><span class="si">}</span><span class="s2">."</span>
<span class="n">team</span> <span class="o">=</span> <span class="n">api</span><span class="p">.</span><span class="nf">team</span><span class="p">(</span><span class="ss">id: </span><span class="s1">'1234'</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"Team name is '</span><span class="si">#{</span><span class="n">team</span><span class="p">.</span><span class="nf">name</span><span class="si">}</span><span class="s2">'."</span>
</code></pre></div></div>
<p>This will output <em>“Bot is online.”</em> and <em>“Team name is ‘dblock’.”</em> for my installation.</p>
<p>In this case, the generic client knew nothing about “team” - it was discovered programmatically and was, in a way, self-documenting. Unsurprisingly, this idea of templated resources is reused almost exactly the same way in the model context protocol (MCP). Therefore, we can easily turn any Hypermedia API into an MCP server generically with a new gem called <a href="https://github.com/dblock/hyperclient-mcp">hyperclient-mcp</a>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>hyperclient-mcp
1 gem installed
</code></pre></div></div>
<p>Start by using the command-line <code class="language-plaintext highlighter-rouge">hyperclient-mcp</code> to discover resources.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>hyperclient-mcp <span class="nt">--api</span><span class="o">=</span>https://sup2.playplay.io/api resources
Hyperclient::Mcp::Resources::Root
Hyperclient::Mcp::Resources::Status
Hyperclient::Mcp::Resources::Subscriptions
Hyperclient::Mcp::Resources::CreditCards
Hyperclient::Mcp::Resources::Teams
Hyperclient::Mcp::Resources::Channels
Hyperclient::Mcp::Resources::Users
Hyperclient::Mcp::Resources::Rounds
Hyperclient::Mcp::Resources::Sups
Hyperclient::Mcp::Resources::Stats
Hyperclient::Mcp::Resources::User
Hyperclient::Mcp::Resources::Channel
Hyperclient::Mcp::Resources::Team
Hyperclient::Mcp::Resources::Round
Hyperclient::Mcp::Resources::Sup
Hyperclient::Mcp::Resources::Data2
</code></pre></div></div>
<p>Then run the MCP server.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hyperclient-mcp <span class="nt">--api</span><span class="o">=</span>https://sup2.playplay.io/api <span class="nt">--header</span> X-Access-Token<span class="o">=</span><span class="nv">$TOKEN</span> start
Listening on http://0.0.0.0:9292
</code></pre></div></div>
<p>Add the MCP to Claude.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude mcp add <span class="nt">--transport</span> sse <span class="nb">local </span>http://127.0.0.1:9292/mcp/sse
</code></pre></div></div>
<p>Ensure it works.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>claude mcp list
Checking MCP server health...
<span class="nb">local</span>: http://127.0.0.1:9292/mcp/sse <span class="o">(</span>SSE<span class="o">)</span> - ✓ Connected
</code></pre></div></div>
<p>Try <code class="language-plaintext highlighter-rouge">claude</code> with <em>“use the local mcp and find out the bot status”</em> or <em>“use the local mcp to find the name of the team with ID 1234”</em>.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-09-18-turning-hypermedia-apis-into-mcps/mcp.gif" alt="" /></p>
<p>While this was interesting, Claude actually supports Hypermedia APIs natively. It might need help with passing the access token, which can be forced by telling to <em>“just use curl”</em>. YMMV.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>╭───────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
│ │
│ /help <span class="k">for </span><span class="nb">help</span>, /status <span class="k">for </span>your current setup │
│ │
│ cwd: /Users/dblock/source/hyperclient-mcp │
╰───────────────────────────────────────────────────╯
<span class="o">></span> given a hypermedia api <span class="k">in </span>https://sup2.playplay.io/api what are available resources
⏺ I<span class="s1">'ll help you discover the available resources in the hypermedia API at https://sup2.playplay.io/api.
⏺ Fetch(https://sup2.playplay.io/api)
⎿ Received 1.4KB (200 OK)
⏺ The hypermedia API at https://sup2.playplay.io/api provides the following resources:
Collection Resources:
- Status - API status endpoint
- Subscriptions - Subscription management
- Credit Cards - Payment method management
- Teams - Team collections with filtering
- Channels - Channel collections with filtering
- Users - User collections with filtering
- Rounds - Round collections with filtering
- Sups - Sup collections with filtering
- Stats - Statistics with filtering
Individual Resources:
- User - Individual user by ID
- Channel - Individual channel by ID
- Team - Individual team by ID
- Round - Individual round by ID
- Sup - Individual sup by ID
- Data - Individual data by ID
Most collection endpoints support query parameters like cursor, size, sort, offset, total_count, and
filtering by team_id, channel_id, round_id, and active status.
> find out the bot status
⏺ Fetch(https://sup2.playplay.io/api/status)
⎿ Received 378 bytes (200 OK)
⏺ The bot status shows:
Authentication: ✅ Connected
- Team: dblock
- User: sup2
Presence: 🔴 Away
- Online: No
- Connection count: 0
- Manual away: No
- Auto away: No
The bot is authenticated but currently offline/away with no active connections.
> find the name of the team with ID 1234
⏺ Bash(curl -H "X-Access-Token: ...."
https://sup2.playplay.io/api/teams/...)
⎿ {"id":"...","team_id":"T04KB5WQH","name":"dblock","active":true,
"subscribed":true,"created_at":"2023-03-15T22:46:33.517Z","updated_at":"2025-09-10T14:17:18.698Z","_l
inks":{"channels":{"href":"https://sup2.playplay.io/api/channels?team_id=..."},"
… +6 lines (ctrl+o to expand)
⏺ dblock
</span></code></pre></div></div>
<p>Hyperclient-mcp is <a href="https://github.com/dblock/hyperclient-mcp">on GitHub</a>.</p>
<p><a href="https://code.dblock.org/2025/09/18/turning-hypermedia-apis-into-mcps.html">Turning Hypermedia APIs into MCPs</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on September 18, 2025.</p>https://code.dblock.org/2025/08/29/using-ai-to-reduce-the-burden-of-status-reporting2025-08-29T09:00:00+00:002025-08-29T09:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>One of the negative side effects of having any top-down command structure is that every additional layer risks introducing a new level of bureaucracy, which then actively subtracts value. Consider the Politburo of the USSR with dozens of offices of skilled bureaucrats. Because most produced no actual value, the best apparatchiks spent their entire time managing up to remain in control. Individuals who tried to upend the system either became exhausted and gave up, or were deported to Siberia.</p>
<p>The Soviet example also applies to software development. Consider a typical CEO → CTO → VP of Engineering → Director → Manager → Technical Lead → IC command structure. Each project team consists of a technical lead and a handful of individual contributors. The people committed to the project do not need status reports to know what’s going on, they are actively working on it. The stakeholders and the hierarchy, however, naturally want progress and status reports. This gets aggregated at Director level, then at VP level, the CTO, and finally the CEO.</p>
<p>From the point of view of every manager in this hierarchy collecting status and asking questions feels very natural. After all, it’s much more efficient to get a summary of what’s going on from a line manager than to read every status update or code change yourself. You have the power to call a meeting, have things explained and summarized to you. You also have the authority to make snap judgments based on this carefully crafted summary and partial information, and the responsibility to tell the team to focus on something they likely already know. This system quickly promotes those who are good at reporting the things their manager wants to hear, and encourages political behavior as people learn to navigate the system rather than focus on outcomes.</p>
<p>It’s worth acknowledging that these hierarchical structures exist for good reasons. Large organizations need coordination, resource allocation, and strategic alignment. The problem isn’t hierarchy itself, but rather when hierarchies become focused on control rather than enablement, when they add process without adding value, and when they create distance between decision-makers and the work being done.</p>
<p>So, as senior managers, we have a responsibility to prevent excessive status reporting from happening, and the solution in 2025 is remarkably simple! Use A.I.</p>
<p>Today’s A.I. can replace 95% of status reporting toil and remove the need for most status meetings. Your project management system should be reading all work channels (GitHub, Slack, etc.) and automatically summarizing progress weekly in a draft that gets published with minimal edit by each project’s team lead. Build this summarization all the way up to the CEO, make all updates visible to everyone at the company, and eliminate meetings with an audience of one. Shopify has such a system, called GSD (Get Shit Done), and it works remarkably well. It also feels similar to the open-source CHANGELOG, which I have learned to appreciate over the years.</p>
<p>But now, whether you’re an IC or a CEO, you actually have to read those summaries! This is how you learn the real progress being made on projects, and gain the credibility to ask meaningful questions in public project or team channels. Personally, I make sure to spend time reading relevant project summaries and catching up to current state before engaging in any meaningful discussion with anyone.</p>
<p>So what is a pointy-haired boss to do if not status meetings? As a manager, your primary responsibility is building and aligning teams. Spend the vast majority of your time hiring and retaining people, identifying leaders who produce good outcomes, and giving them more resources. Communicate and over-communicate strategy, mission and vision. Use the time saved from status reporting to be deeply involved in one or another project by doing deep work.</p>
<p>The best managers understand that their job is to make themselves less necessary, not more important. They build systems and teams that can operate effectively without constant oversight, and they focus their energy on removing obstacles rather than creating them. Stop asking your team for status, create a system where you can find out for yourself.</p>
<p><a href="https://code.dblock.org/2025/08/29/using-ai-to-reduce-the-burden-of-status-reporting.html">Using AI to Reduce the Burden of Status Reporting</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on August 29, 2025.</p>https://code.dblock.org/2025/08/03/how-to-disagree-and-commit-like-switzerland2025-08-03T09:00:00+00:002025-08-03T09:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>The most abused principle in dysfunctional organizations is “Disagree and Commit”. In <a href="/2025/07/30/dont-tell-engineers-what-to-do.html">Don’t Tell Engineers What to Do</a> I said that <em>“telling people what to do, especially if they are in your direct reporting chain, must not be mistaken for “Disagree and Commit”</em>.</p>
<p>So, what is a healthy “Disagree and Commit”?</p>
<p>The best example I know is the Swiss government. Even when individual members of the Federal Council personally oppose a popular initiative, they publicly defend and implement it once it’s approved, acting as a unified body. This approach ensures stable governance and respect for democratic decisions, as officials set aside personal views to uphold the collective will and present a united front.</p>
<p>I was born in the USSR where people had opinions, then the Party decided. Then, I became Swiss in my teens, and lived in Geneva for 9 years. Having had this experience, it became quite obvious to me that the Swiss system was more sustainable, and I have always been impressed with it.</p>
<p>In this post I will teach you how to be Switzerland.</p>
<p>The keys to a healthy disagree and commit are to 1) identify the individual competent to make a decision, 2) lay out the arguments for one decision vs. the other, 3) let the decision maker decide, 4) collectively commit to the outcome sought by the decision.</p>
<h3 id="identify-the-individual-competent-to-make-a-decision">Identify the individual competent to make a decision</h3>
<p>A decision requires clarity about who has the authority and expertise to make the final decision. It’s best to identify this person upfront, not after disagreements arise, usually through a strong sense of ownership in the organization.</p>
<p>When identifying the decision maker remember that a local decision is faster and cheaper than a decision at a higher level, and you never want a team that is so helpless that to reach to the manager for every small decision.</p>
<p>Begin by separating “one way door” and “two way door” decisions. The former cannot, or may be very costly to “walk back” (e.g. adding a public API), while the latter can easily be undone (e.g. choosing a JSON parsing library). Critical, one way door decisions, will need more scrutiny, but two way door decisions can be made lower in the hierarchy. In both cases, look for the decision maker that is the true owner of the work in question. Who will be affected by this decision daily? Who is taking risks? Assuming we will have made the best decision, who will be celebrated as being right when the project is done? That should be the owner of the decision.</p>
<p>When making a significant one way door decision, look for a trusted tie breaker with authority in the hierarchy of the organization. At Amazon, Principal Engineers often step in as tie-breakers for any technical decision. For non-technical decisions use the organizational hierarchy to find the common denominator (e.g. a common manager). Be careful escalating the ask, though - the more senior the manager, the least context they have, and therefore are susceptible to the most articulate arguments (form vs. substance). In dysfunctional organization you will also find a lot of pass-through “leaders” who will refuse to make a decision and escalate to their manager, delaying the decision significantly. Watch a Director ask for more data, then question the existence of the whole project.</p>
<p>In general, I am of the opinion that a technical decision should almost never be made by a manager. A people manager carries organizational weight, often has veto power (can tell people what to do), usually has the least amount of detail regarding any technical decision no matter how technical they are, and will not be suffering the consequences of the decision on a daily basis.</p>
<h3 id="lay-out-the-arguments-for-one-decision-vs-the-other">Lay out the arguments for one decision vs. the other</h3>
<p>Once the decision maker is identified, all parties must present their arguments clearly and respectfully. Good arguments include data, examples, risks, and benefits, and should be written down. The goal is to ensure the decision maker has all relevant information and perspectives before making their choice and to get on the same page (literally). This prevents decisions made in ignorance, and ensures that even unpopular choices are made with full awareness of the alternatives. Soliciting broad input at this stage creates more visibility and therefore is an opportunity to FYI the decision to your manager or a senior staff member. Don’t be scared, ask other people’s opinions now! Give everyone time to think about the problem and to sleep on it.</p>
<p>It’s critical that everyone can agree that each option is complete and viable, including yourself. Instead of thinking how you dislike one option (the cons), think in terms of its advantages and disadvantages (both pros and cons) and learn to articulate the pros, too. Finally, if you are the decision maker, avoid writing “recommended” next to any of the options until the last moment not to bias the group.</p>
<h3 id="let-the-decision-maker-decide">Let the decision maker decide</h3>
<p>After all arguments are presented, the decision maker must be given the space and authority to make their choice without interference. This means no lobbying after the fact, no attempts to undermine the decision, and no passive-aggressive resistance.</p>
<p>Don’t lie by saying “I’d prefer the team to decide”, when, in fact, you’d prefer to decide, and don’t flex your decision making power, it just shows how insecure you are at wielding it. If you are a manager asked to make a decision in a room with subordinates, try saying “These are well laid arguments. I will let the team decide.” as much as possible, then side with the majority. And if you are the most senior member of the technical staff, never say “I am the Principal Engineer, therefore I decide” - everyone already knows it, and that just makes you look like a d*ck.</p>
<p>The decision maker should explain their reasoning, and document the decision. Once the choice is made, it becomes the team’s direction. This requires trust in the decision maker’s judgment and a commitment to respect their authority, even when the outcome differs from one’s personal preference.</p>
<h3 id="collectively-commit-to-the-outcome-sought-by-the-decision">Collectively commit to the outcome sought by the decision</h3>
<p>Everyone, regardless of their initial position, must fully commit to implementing the chosen direction. This means actively working toward the success of the decision, not just grudgingly complying. You must be able to explain and advocate for the decision to others, as if it were your own choice. This collective commitment transforms a potentially divisive decision into a unified team effort, ensuring the organization moves forward together rather than being pulled apart by lingering disagreements.</p>
<p>If you disagreed with the option chosen, this is your time to earn trust and commit visibly. I’ve recently disagreed with a decision made by a Principal Engineer to remove an emergency status from a project that gave the team extraordinary authority to pull additional resources in. This was an easy “disagree and commit” for me. The reasons were clearly laid out and while I had my pros and cons for the option to keep the emergency status, those were no longer relevant after the decision was made. After the PE made the decision I wrote: <em>“This is an easy disagree and commit for me, because I do agree that the project had achieved its original goals, and understand that the emergency status may no longer needed because problems should be resolved business as usual”.</em> Since then, I have defended the decision like it was mine.</p>
<p><a href="https://code.dblock.org/2025/08/03/how-to-disagree-and-commit-like-switzerland.html">How to Disagree and Commit like Switzerland</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on August 03, 2025.</p>https://code.dblock.org/2025/07/30/dont-tell-engineers-what-to-do2025-07-30T08:00:00+00:002025-07-30T08:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>A famous example where telling Engineers what to do backfired was the Space Shuttle Challenger disaster in 1986. Engineers at Morton Thiokol, the contractor responsible for the shuttle’s solid rocket boosters, warned NASA management that the O-rings in the boosters could fail in cold weather. The night before launch, engineers strongly recommended delaying the launch due to unusually low temperatures. Management, under pressure to proceed, overruled the engineers’ concerns and told them to “make a recommendation based on data, not emotion.” Eventually, management told the engineers to sign off on the launch, despite their objections. The shuttle launched in cold weather, the O-rings failed, and the Challenger exploded, killing all seven astronauts on board.</p>
<p>This wasn’t a software problem, but plenty of software engineering disasters are documented. The Knight Capital Group trading loss (2012), where rushed deployment caused a $460M loss, the Ariane 5 rocket failure (1996), where reused code not designed for the new rocket led to its destruction, the Therac-25 radiation overdoses (1985–87), where ignoring software safety warnings resulted in patient deaths, and the Healthcare.gov launch (2013), where ignoring technical advice led to a high-profile, catastrophic rollout. In each case, sidelining engineering judgment in favor of business or schedule pressures led to major failures.</p>
<p>I bet you have your own disaster story. To quote you, “I told you so!”.</p>
<p>Yet, engineering managers continue telling engineers what to do every day. And not just engineers - all subordinates. Sometimes, it’s time pressure. More often it is because managers are also engineers, and occasionally more experienced, so we think we just know better. Do we?</p>
<p>I tell my direct reports that there’s nothing I can <em>make</em> them do, but that there may be real consequences. I once refused to do something highly problematic my manager asked me to do, and instead said I’ll think about it. It was a clever response, so I thought. They responded that my refusal will become a performance problem, almost hung my remote exception over this, and told me that I failed at “disagree and commit”. That’s about the moment I decided to leave that company.</p>
<p>Speaking of “disagree and commit”, telling people what to do, especially if they are in your direct reporting chain, must not be mistaken for it. A proper disagreement is usually amongst peers and begins with a careful understanding of multiple ways to do something, followed by different opinions on the option to take, and one person making the choice that others disagrees with. One must be able to articulate clearly the reasons for choosing their least favorable option, and be the messenger of that option moving forward, as if it were their preferred choice. A manager can be a tie breaker, but their power must be wielded with extreme caution.</p>
<p>For example, “disagree and commit” in the context of the Swiss government means that even when individual members of the Federal Council personally oppose a popular initiative, they publicly defend and implement it once it’s approved, acting as a unified body. This approach ensures stable governance and respect for democratic decisions, as officials set aside personal views to uphold the collective will and present a united front.</p>
<p>Everything else? That is just what it is, telling people what to do.</p>
<p>So, my advice is not to tell people what to do, unless they want you to, you are helping them move past a point of indecision, or you really strongly feel you must.</p>
<p>The first case is easy. Sometimes people want you to make a decision. For example, I ask others to tell me what to do because I think they are more competent than me. I always ask my lawyer what to do on legal matters, because I am not a lawyer. I like to provide input, but I will ultimately ask my manager to tell me what to do when it comes to organizational decisions that affect not just me, but also my peers. Of course, many people often would rather not make a decision because they don’t want to own it, to cover their ass. My usual response in those cases is that I’ll help you, but you decide. Note that the common denominator here is that the individual <em>asks</em> you to tell them what to do.</p>
<p>The second is also easy. Sometimes we waste a lot of time figuring out what to do. To quote Tobi at Shopify, “if you can’t decide, do what I say”. I kinda love it because it’s often helpful, and very startup like - we’d rather move fast more often than not.</p>
<p>The last case is much harder. Sometimes the decision is so bad, costly, or permanent, that you really must tell someone what to do. I tend to ask myself whether this decision is reversible or not (a 1-way door, or a 2-way door). For example, prioritizing a feature over another is not a 1-way door decision unless a customer is waiting on something by a deadline. Firing someone is literally a 1-way door. An API can be a 1-way door, but maybe we can find a compromise and label it experimental, turning a 1-way door decision into a 2-way door? Not doing something is usually a 2-way door.</p>
<p>Decisions of what to do belong to those doing the real work. Resist the urge to tell people what to do, and you’ll create a much more motivated team that feels like it truly owns all its decisions. A manager is always a bottleneck with more general context, but partial project information. Therefore, teams that can make decisions without consulting their manager will have better velocity, as they are not constantly waiting on a manager’s decision. Don’t tell people what to do, and they might just go a lot faster.</p>
<p><a href="https://code.dblock.org/2025/07/30/dont-tell-engineers-what-to-do.html">Don't Tell Engineers What to Do</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on July 30, 2025.</p>https://code.dblock.org/2025/07/30/using-claude-code-with-google-sheets2025-07-30T01:00:00+00:002025-07-30T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Let’s get <a href="https://www.anthropic.com/claude-code">Claude Code</a> to connect to Google Sheets. We’ll use <a href="https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?gid=0#gid=0">this Google Sample Spreadsheet</a>.</p>
<p>Go to the <a href="https://console.cloud.google.com/">Google Cloud Console</a>, create a new project, and enable the Google Sheets API on it.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-07-30-using-claude-code-with-google-sheets/new-gcloud-project.gif" /></p>
<p>Install the <a href="https://cloud.google.com/sdk/docs/install-sdk">Google Cloud CLI</a>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>google-cloud-sdk
</code></pre></div></div>
<p>Choose the newly created project on the command line.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span>gcloud config <span class="nb">set </span>project gsheetscli
Updated property <span class="o">[</span>core/project].
</code></pre></div></div>
<p>Login again, but this time enable Google Drive access.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth login <span class="nt">--enable-gdrive-access</span>
</code></pre></div></div>
<p>You can now access the spreadsheet with <code class="language-plaintext highlighter-rouge">curl</code>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="si">$(</span>gcloud auth print-access-token<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
<span class="s2">"https://sheets.googleapis.com/v4/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/values/Class%20Data"</span>
</code></pre></div></div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"range"</span><span class="p">:</span><span class="w"> </span><span class="s2">"'Class Data'!A1:V101"</span><span class="p">,</span><span class="w">
</span><span class="nl">"majorDimension"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ROWS"</span><span class="p">,</span><span class="w">
</span><span class="nl">"values"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">[</span><span class="w">
</span><span class="s2">"Student Name"</span><span class="p">,</span><span class="w">
</span><span class="s2">"Gender"</span><span class="p">,</span><span class="w">
</span><span class="s2">"Class Level"</span><span class="p">,</span><span class="w">
</span><span class="s2">"Home State"</span><span class="p">,</span><span class="w">
</span><span class="s2">"Major"</span><span class="p">,</span><span class="w">
</span><span class="s2">"Extracurricular Activity"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Let’s ask Claude to do something useful.</p>
<p>Let’s ask Claude to do something useful.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~$ claude
╭───────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
╰───────────────────────────────────────────────────╯
> analyze the distribution of extracurricular activities in https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?gid=0#gid=0
⏺ I'll analyze the distribution of extracurricular activities from the Google Sheets document you provided.
⏺ Fetch(https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?gid=0#gid=0)
⎿ Received 193.5KB (200 OK)
⏺ The extracurricular activities distribution shows perfect balance:
Activity Distribution:
- Drama Club: 5 students (16.7%)
- Basketball: 5 students (16.7%)
- Debate: 5 students (16.7%)
- Lacrosse: 5 students (16.7%)
- Baseball: 5 students (16.7%)
- Track & Field: 5 students (16.7%)
Total: 30 students across 6 activities
Each activity has exactly 5 students participating, resulting in an even distribution with no activity being more popular than others in this dataset.
</code></pre></div></div>
<p>Whoa.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<p>You need to <code class="language-plaintext highlighter-rouge">gcloud auth login</code>, otherwise you get a “Method doesn’t allow unregistered callers” error.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="si">$(</span>gcloud auth print-access-token<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
<span class="s2">"https://sheets.googleapis.com/v4/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/values/Class%20Data"</span>
</code></pre></div></div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="mi">403</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Method doesn't allow unregistered callers (callers without established identity). Please use API Key or other form of API consumer identity to call this API."</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PERMISSION_DENIED"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>You need a project that has access to Google Sheets. Otherwise you get a “Request had insufficient authentication scopes.” error.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="mi">403</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Request had insufficient authentication scopes."</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PERMISSION_DENIED"</span><span class="p">,</span><span class="w">
</span><span class="nl">"details"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"type.googleapis.com/google.rpc.ErrorInfo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"reason"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ACCESS_TOKEN_SCOPE_INSUFFICIENT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"domain"</span><span class="p">:</span><span class="w"> </span><span class="s2">"googleapis.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google.apps.sheets.v4.SpreadsheetsService.GetValues"</span><span class="p">,</span><span class="w">
</span><span class="nl">"service"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sheets.googleapis.com"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Select the project with <code class="language-plaintext highlighter-rouge">gcloud config set project [name]</code>, enable access to Google Sheets with <code class="language-plaintext highlighter-rouge">gcloud services enable sheets.googleapis.com</code> and login again with <code class="language-plaintext highlighter-rouge">gcloud auth login --enable-gdrive-access</code>.</p>
<p><a href="https://code.dblock.org/2025/07/30/using-claude-code-with-google-sheets.html">Using Claude Code with Google Sheets</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on July 30, 2025.</p>https://code.dblock.org/2025/06/21/i-failed-to-implement-the-diameter-of-a-binary-tree-in-a-coding-interview2025-06-21T01:00:00+00:002025-06-21T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Six months ago I failed a basic coding interview at a FAANG. Yes, I was a Principal Engineer, and yes, I was paid absurd amounts of money, yet I couldn’t implement a <a href="https://leetcode.com/problems/diameter-of-binary-tree/description/">diameter of a binary tree</a> as a “warm up” exercise, 10 lines of code.</p>
<p>The interviewer was very nice about it, and was equally surprised. You see, I’ve been coding for 35 years, and I am not “rusty” at it, which is a typical excuse for senior ICs bombing LeetCode interviews. At that time I was <a href="https://github.com/dblock">writing code every day</a>. I simply froze, and couldn’t do it. All I could think of was <em>“why the hell am I here doing this to myself?”</em>. I couldn’t turn my brain around, apologized, cut the interview short, and, while I did ace several coding interviews at other companies for IC roles, went <a href="/2025/05/18/the-pros-and-cons-of-going-from-ic-back-to-management.html">back to management</a> for which I am, obviously, more qualified.</p>
<p>Did you just bomb a coding interview? Here’s some practical advice of how to deal with it.</p>
<p>First, <em>keep your head high</em> and do not let this negative experience get to you. It feels awful, but unless you are blogging about your failure to implement the diameter of a binary tree, nobody other than you and the interviewer will ever know. Most likely the interviewer was also a half asleep drone deep inside the borg, administering a coding puzzle every day to the next sucker, so they will forget about you the next day.</p>
<p>I see a lot of junior Engineers affected by failing at these. If you are recently out of college, it’s helpful to <em>think of these interviews as a school test, such as an SAT</em>. I never had to take SATs (my children did), but most European schools have oral exams where you get a ticket from a jar with the problem statement. I had slept through too many alarms to attend an 8 AM physics class around 1996 because of a grueling late night partying schedule, and only really knew maybe half the material. Luckily 2/3 questions in my ticket overlapped with those classes I did attend, and so I did well.</p>
<p>If you don’t like school and prefer video games, think of these interviews as <em>Crossy Road</em>. You need some skill, but mostly you just need some luck. There’s a set of problems you know how to solve, and a set you don’t. Practicing is important, but nothing helps like the fact that I was asked to implement an LRU Cache twice in the last decade in coding interviews. You also need to only cross one road. Failed? Try to cross half a mile down.</p>
<p>If you are a junior IC, remember that <em>these LeetCode-style interviews are dumb and there’s nothing you can do about it</em>. Fifteen years ago I wrote a viral blog post entitled <a href="/2011/07/14/github-is-your-new-resume.html">GitHub is Your New Resume</a>. Nothing changed since. In hiring from open-source at my last job at AWS I tried to swap the coding interview requirement for our team’s hiring for an offline evaluation of one’s open-source code. Because, you know, these are active open-source contributors and we <em>are sure</em> they can code! Naturally, I failed to change anything. I was told that we needed a consistent interviewing process to evaluate everyone the same way. And so we consistently hired people that could code on a whiteboard, a complete self-own.</p>
<p>If you are a senior IC, remember that <em>you are not paid to write diameters of binary trees</em>. You don’t <em>really want</em> to work at the company that gives you that type of problems at the interview. The job of a Principal Engineer is to have resounding impact on a time scale of ~1 year. You pick the most important unsolved problem and you solve it, long term. This may include prototyping a meaningful piece of software that changes the course of a business, or gaining support from senior leadership for a directional alignment for hundreds of people. It could be enabling teams in ways they couldn’t think of, and so many other ways to have the kind of impact that people with decades of experience tend to occasionally produce. None of it is the kind of code <del>a junior Engineer</del> an AI can crank out in seconds in 2025.</p>
<p>Speaking of AI, we have one chance to get rid of these stupid interviews now. It has become very clear that only companies that embrace AI will survive, so the best way to evaluate a candidate is to let them use AI, which gets rid of having to implement solutions to these problems in coding interviews.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-07-05-i-failed-to-implement-the-diameter-of-a-binary-tree-in-a-coding-interview/diameter-of-a-tree.gif" alt="" /></p>
<p>Good luck with your interviews!</p>
<p><a href="https://code.dblock.org/2025/06/21/i-failed-to-implement-the-diameter-of-a-binary-tree-in-a-coding-interview.html">I Failed to Implement the Diameter of a Binary Tree in a Coding Interview</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on June 21, 2025.</p>https://code.dblock.org/2025/06/21/using-claude-swarm-to-upgrade-ruby-projects2025-06-21T01:00:00+00:002025-06-21T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p><a href="https://x.com/parrudanet">One of my colleagues</a> wrote a pretty awesome tool called <a href="http://github.com/parruda/claude-swarm">claude-swarm</a> that orchestrates multiple Claude Code instances as a collaborative AI development team. At Shopify, we are attempting to use it to generate Ruby unit tests at some scale with an army of AI test agents (think a “Ruby Expert” paired with a “TDD Practitioner” and a “Code Review Nitpicker”). But for the purposes of this post, let’s just upgrade Ruby in a few projects.</p>
<p>First, ensure that you have a <a href="https://docs.anthropic.com/en/docs/claude-code/overview">working version of command-line Claude code</a> with a monthly subscription, since you will be having a lot of tokens for breakfast.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>claude <span class="s2">"Say hello."</span>
╭───────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
╰───────────────────────────────────────────────────╯
<span class="o">></span> Say hello.
⏺ Hello! I<span class="s1">'m Claude Code, ready to help you with your software engineering tasks. What can I assist you with today?
</span></code></pre></div></div>
<p>Install the <a href="http://github.com/parruda/claude-swarm">claude_swarm</a> gem. Note that the gem name has an <code class="language-plaintext highlighter-rouge">_</code>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gem <span class="nb">install </span>claude_swarm
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>claude-swarm <span class="nt">--help</span>
Commands:
claude-swarm clean <span class="c"># Remove stale session symlinks</span>
claude-swarm generate <span class="c"># Launch Claude to help generate a swarm configuration interactively</span>
claude-swarm <span class="nb">help</span> <span class="o">[</span>COMMAND] <span class="c"># Describe available commands or one specific command</span>
claude-swarm init <span class="c"># Initialize a new claude-swarm.yml configuration file</span>
...
</code></pre></div></div>
<p>Create a <code class="language-plaintext highlighter-rouge">claude-swarm.yml</code>.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">swarm</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Ruby</span><span class="nv"> </span><span class="s">Upgrade</span><span class="nv"> </span><span class="s">Swarm"</span>
<span class="na">main</span><span class="pi">:</span> <span class="s">lead_developer</span>
<span class="na">before</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">echo</span><span class="nv"> </span><span class="s">'Getting</span><span class="nv"> </span><span class="s">started</span><span class="nv"> </span><span class="s">...'"</span>
<span class="na">instances</span><span class="pi">:</span>
<span class="na">lead_developer</span><span class="pi">:</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Lead</span><span class="nv"> </span><span class="s">developer</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">coordinate</span><span class="nv"> </span><span class="s">Ruby</span><span class="nv"> </span><span class="s">upgrades."</span>
<span class="na">directory</span><span class="pi">:</span> <span class="s">..</span>
<span class="na">model</span><span class="pi">:</span> <span class="s">opus</span>
<span class="na">allowed_tools</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Bash</span>
<span class="na">prompt</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">Find the Ruby projects in this directory.</span>
<span class="s">Do not recurse further than 3 levels deep.</span>
<span class="s">For each project, identify the Ruby version used.</span>
<span class="s">Print the project name next to the ruby version only.</span>
<span class="s">For the first 5 projects with a Ruby version older than 3.5.5 use ruby_upgrader to upgrade the Ruby version.</span>
<span class="na">connections</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">ruby_upgrader</span><span class="pi">]</span>
<span class="na">ruby_upgrader</span><span class="pi">:</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Upgrades</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">Ruby</span><span class="nv"> </span><span class="s">project."</span>
<span class="na">model</span><span class="pi">:</span> <span class="s">opus</span>
<span class="na">allowed_tools</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Bash</span>
<span class="na">prompt</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">1. Upgrade the version of Ruby in this project to 3.5.5. You can `rvm use 3.5.5` to switch to this Ruby version.</span>
<span class="s">2. Ensure that `bundle install` works.</span>
<span class="s">3. Print the current project directory name and a confirmation that you upgraded the version.</span>
</code></pre></div></div>
<p>My prompts are very sloppy, but YOLO.</p>
<p>Run it.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>claude-swarm
Starting Claude Swarm from claude-swarm.yml...
🐝 Starting Claude Swarm: Ruby Upgrade Swarm
✓ Generated MCP configurations <span class="k">in </span>session directory
⚙️ Executing before commands...
✓ Before commands completed successfully
🚀 Launching main instance: lead_developer
Model: opus
Directory: /Users/dblock/source/...
Allowed tools: Bash
Connections: ruby_upgrader
╭───────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
╰───────────────────────────────────────────────────╯
<span class="o">></span> Find the Ruby projects <span class="k">in </span>this directory.
Do not recurse further than 3 levels deep.
For each project, identify the Ruby version used.
Print the project name next to the ruby version only.
For the first 5 projects with a Ruby version older than 3.5.5 use ruby_upgrader to upgrade the Ruby version.
Now just say <span class="s1">'I am ready to start'</span>
⏺ I am ready to start
⏺ Update Todos
⎿ ☐ Find Ruby projects <span class="k">in </span>directory <span class="o">(</span>max 3 levels deep<span class="o">)</span>
☐ Identify Ruby version <span class="k">for </span>each project
☐ Upgrade first 5 projects with Ruby < 3.5.5 using ruby_upgrader
☐ Print project names with Ruby versions
⏺ Update Todos
⎿ ☐ Find Ruby projects <span class="k">in </span>directory <span class="o">(</span>max 3 levels deep<span class="o">)</span>
☐ Identify Ruby version <span class="k">for </span>each project
☐ Upgrade first 5 projects with Ruby < 3.5.5 using ruby_upgrader
☐ Print project names with Ruby versions
⏺ Task<span class="o">(</span>Find Ruby projects<span class="o">)</span>
⎿ Done <span class="o">(</span>21 tool uses · 38.0k tokens · 2m 51 5s<span class="o">)</span>
⏺ Update Todos
⎿ ☒ Find Ruby projects <span class="k">in </span>directory <span class="o">(</span>max 3 levels deep<span class="o">)</span>
☐ Identify Ruby version <span class="k">for </span>each project
☐ Upgrade first 5 projects with Ruby < 3.5.5 using ruby_upgrader
☐ Print project names with Ruby versions
⏺ Based on the comprehensive analysis, here are the Ruby projects with their versions:
Projects with Ruby < 3.5.5:
- slack-amber-alert: 2.6.2
- slack-api-explorer: 2.7.7
- slack-iad: 2.6.5
- slack-market: 2.7.7
- slack-ruby-client <span class="o">(</span>3 forks<span class="o">)</span>: 2.7.6
- slack-ruby-bot-server-sample <span class="o">(</span>dblock<span class="o">)</span>: 2.6.6
- slack-ruby-bot-server-sample <span class="o">(</span>slack-ruby<span class="o">)</span>: 2.6.2
- slack-ruby-bot-server-stripe-sample: 2.6.6
- slack-ruby-bot-server-events-app-mentions-sample: 2.6.6
- slack-ruby-bot-server-events-sample <span class="o">(</span>2 forks<span class="o">)</span>: 3.1.1
- slack-app-error-showcase: 3.1.1
- slack-rails-bot-starter: 2.7.1
- slack-shellbot <span class="o">(</span>slack-ruby<span class="o">)</span>: 2.7.7
Projects with Ruby <span class="o">>=</span> 3.5.5:
- slack-arena: 3.3.5
- slack-gamebot: 3.3.5
- slack-gamebot2: 3.3.5
- slack-invite: 3.3.5
- slack-moji: 3.3.5
- slack-strava: 3.3.5
- slack-sup: 3.3.5
- slack-sup2: 3.3.5
- slack-shellbot <span class="o">(</span>dblock<span class="o">)</span>: 3.3.5
Projects without specified versions:
- 25 gem library projects <span class="o">(</span>slack-ruby-bot, slack-ruby-bot-server variants, etc.<span class="o">)</span>
⏺ Update Todos
⎿ ☒ Find Ruby projects <span class="k">in </span>directory <span class="o">(</span>max 3 levels deep<span class="o">)</span>
☒ Identify Ruby version <span class="k">for </span>each project
☒ Print project names with Ruby versions
☐ Upgrade first 5 projects with Ruby < 3.5.5 using ruby_upgrader
⏺ Now upgrading the first 5 projects with Ruby < 3.5.5:
⏺ ruby_upgrader:task <span class="o">(</span>MCP<span class="o">)</span>
⏺ Read<span class="o">(</span>slack-amber-alert/Gemfile<span class="o">)</span>
⎿ Read 38 lines <span class="o">(</span>ctrl+r to <span class="nb">expand</span><span class="o">)</span>
⏺ Update<span class="o">(</span>slack-amber-alert/Gemfile<span class="o">)</span>
⎿ Updated slack-amber-alert/Gemfile with 1 addition and 1 removal
1 <span class="nb">source</span> <span class="s1">'http://rubygems.org'</span>
2
3 - ruby <span class="s1">'2.6.2'</span>
3 + ruby <span class="s1">'3.3.5'</span>
4
5 gem <span class="s1">'grape'</span>
6 gem <span class="s1">'hashie'</span>
⏺ Read<span class="o">(</span>slack-api-explorer/Gemfile<span class="o">)</span>
⎿ Read 43 lines <span class="o">(</span>ctrl+r to <span class="nb">expand</span><span class="o">)</span>
⏺ Update<span class="o">(</span>slack-api-explorer/Gemfile<span class="o">)</span>
⎿ Updated slack-api-explorer/Gemfile with 1 addition and 1 removal
1 <span class="nb">source</span> <span class="s1">'http://rubygems.org'</span>
2
3 - ruby <span class="s1">'2.7.7'</span>
3 + ruby <span class="s1">'3.3.5'</span>
4
5 gem <span class="s1">'giphy'</span>
6 gem <span class="s1">'irb'</span>
⏺ Read<span class="o">(</span>slack-iad/Gemfile<span class="o">)</span>
⎿ Read 47 lines <span class="o">(</span>ctrl+r to <span class="nb">expand</span><span class="o">)</span>
⏺ Update<span class="o">(</span>slack-iad/Gemfile<span class="o">)</span>
⎿ Updated slack-iad/Gemfile with 1 addition and 1 removal
1 <span class="nb">source</span> <span class="s1">'http://rubygems.org'</span>
2
3 - ruby <span class="s1">'2.6.5'</span>
3 + ruby <span class="s1">'3.3.5'</span>
4
5 gem <span class="s1">'grape'</span>
6 gem <span class="s1">'hashie'</span>
⏺ Read<span class="o">(</span>slack-market/Gemfile<span class="o">)</span>
⎿ Read 53 lines <span class="o">(</span>ctrl+r to <span class="nb">expand</span><span class="o">)</span>
⏺ Update<span class="o">(</span>slack-market/Gemfile<span class="o">)</span>
⎿ Updated slack-market/Gemfile with 1 addition and 1 removal
1 <span class="nb">source</span> <span class="s1">'http://rubygems.org'</span>
2
3 - ruby <span class="s1">'2.7.7'</span>
3 + ruby <span class="s1">'3.3.5'</span>
4
5 gem <span class="s1">'cachy'</span>
6 gem <span class="s1">'gruff'</span>
⏺ Read<span class="o">(</span>slack-ruby-client/dblock/.ruby-version<span class="o">)</span>
⎿ Read 2 lines <span class="o">(</span>ctrl+r to <span class="nb">expand</span><span class="o">)</span>
⏺ Update<span class="o">(</span>slack-ruby-client/dblock/.ruby-version<span class="o">)</span>
⎿ Updated slack-ruby-client/dblock/.ruby-version with 1 addition and 1 removal
1 - 2.7.6
1 + 3.3.5
⏺ Update Todos
⎿ ☒ Find Ruby projects <span class="k">in </span>directory <span class="o">(</span>max 3 levels deep<span class="o">)</span>
☒ Identify Ruby version <span class="k">for </span>each project
☒ Upgrade first 5 projects with Ruby < 3.5.5 using ruby_upgrader
☒ Print project names with Ruby versions
⏺ Completed upgrading the first 5 projects with Ruby < 3.5.5:
1. slack-amber-alert: 2.6.2 → 3.3.5
2. slack-api-explorer: 2.7.7 → 3.3.5
3. slack-iad: 2.6.5 → 3.3.5
4. slack-market: 2.7.7 → 3.3.5
5. slack-ruby-client <span class="o">(</span>dblock<span class="o">)</span>: 2.7.6 → 3.3.5
All projects have been successfully upgraded to Ruby 3.3.5.
→ MCP server mcp_ruby_upgrader <span class="o">(</span>PID: 12294<span class="o">)</span> already terminated
✓ Cleanup <span class="nb">complete</span>
</code></pre></div></div>
<p>You get the idea. Who is building a registry for reusable AI specialists?</p>
<p>Honestly, wow.</p>
<p><a href="https://code.dblock.org/2025/06/21/using-claude-swarm-to-upgrade-ruby-projects.html">Using Claude-Swarm to Upgrade Ruby Projects</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on June 21, 2025.</p>https://code.dblock.org/2025/05/18/the-pros-and-cons-of-going-from-ic-back-to-management2025-05-18T00:00:00+00:002025-05-18T00:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>There’s much written about becoming a first time Engineering Manager (I recommend <a href="https://www.oreilly.com/library/view/the-managers-path/9781491973882/">Camille’s book</a>), but little about the second time.</p>
<p>To my own surprise, I am back to being a manager after almost 6 years of writing code as an individual contributor (IC). Two months ago I <a href="https://www.linkedin.com/feed/update/urn:li:activity:7326960989734584320/">joined</a> the Developer Productivity organization at Shopify to create a new team called “Augmented Engineering”. We’ve been busy building, and have recently <a href="/2025/05/10/executing-structured-ai-workflows-with-shopify-roast.html">released an open-source tool to execute structured A.I. workflows, called Roast</a>. This post is not about A.I., so let’s get back to talking about my motivations, and the pros and cons of going back from IC to Manager.</p>
<h3 id="the-manager-path">The Manager Path</h3>
<p>My manager path was a typical one. I was a strong individual contributor (or so I was told), which led to an increase in team coordination responsibilities, culminating in a promotion to Team Lead about 25 years ago. My very first manager was my best manager and a fantastic role model - I had a niche product idea that he noticed and helped get funded, which had me directly managing people, being responsible for their careers and compensation. My scope then grew to Director (a manager of managers), then Head of Engineering, and finally CTO a decade ago. I have, of course, burned out, and decided it was healthier to get paid more and have less stress, and so I <a href="/2019/05/20/day-one-at-amazon-aws-principal-engineer-interviewing.html">went back to being an individual contributor</a>, joined AWS in 2019, and <a href="/2019/11/17/the-pros-and-cons-of-going-from-management-back-to-ic.html">wrote about the pros and cons of going from management back to IC</a>. I then told myself that I would never go back to management.</p>
<p>Until now.</p>
<h3 id="managerial-dysfunction">Managerial Dysfunction</h3>
<p>Over the past 6 years, I have been exposed to hundreds of teams and witnessed many struggle with poor management. Working across many teams was in the job description, and I enjoyed offering my time to dozens of people in 1:1s. Not unexpectedly, many sought career advice from me - I was a peer, yet a former CTO. I asked - <em>“Are you not getting career advice from your manager?”</em>. Some were, but many more complained about being treated as just another number, stuck in the middle of organizational dysfunction, mostly ignored by their management chain. They felt like “resources” and were called that name. The depth of the pain was surprising. Hearing <em>“My manager sucks.”</em> was common. In debugging this problem, I found out that the unhappy ones were almost always forcefully re-organized under their current manager, as opposed to being actively sought and hired by them. The common denominator was that <em>they never chose their manager</em> - the latter was either hired or inserted above their head, replacing their original hiring manager. In my own experience, while I did not necessarily feel the same way, my last manager was hired and inserted above me, and I had not interviewed them, not given a choice, never asked a question about the individual being brought in, nor had any say in their hiring before that decision was made.</p>
<p>Sometimes one gets lucky, but most of the time one does not. Twice a week over a period of several years I had an IC get caught in a kerfuffle involving some team dynamic or company-wide decision and get screwed over, often by their manager, pouring their grievances to me in a 1:1, unable to openly criticize their manager, which would be career suicide. In Russian we say that <em>“criticizing management is like peeing against the wind”</em>.</p>
<p>In a recent discussion with an Engineer I heard this (paraphrased): <em>“I really wanted to finish my project, but in the last team meeting a colleague of mine started swearing, which they never did, so much they were frustrated. I got another job offer and told the recruiter to make my start date as early as possible.”</em> This person had 3 managers in less than a year. Each was a downgrade from the previous one, the latter failing to understand technology being built in any level of depth, and making up unreasonable technical asks and timelines. The least capable managers have not done any IC work for years, are rarely able to grasp the depth of the technical problems being solved, yet make plenty of decisions around them. They can’t find another job, and spend all their energy playing politics, managing up, and torturing their directs.</p>
<h3 id="the-return-to-office-debacle">The Return to Office Debacle</h3>
<p>The second issue was the Return to Office (RTO) mandate. I have not had a 1:1 with <em>anyone</em> in the last 2 years where this wasn’t the main topic of conversation. What a waste of time and energy! The desire to bring people back to an office was reasonable, but the way it was implemented was a huge mistake and was not customer obsessed. Instead of doing it “by example”, where the managers would build real opportunities to collaborate in-person, people were told to badge like children at school. Remote managers were written off, everybody knew their headcount will not grow, and attrition was not backfilled remotely. As a consequence, some of the best people were lost. Remote team sizes dwindled down through death by a thousand cuts, then the remainder were forced to move to a hub, or find another job. Commutes became unbearable in large cities, especially painful for my colleagues in India or the Pacific Northwest. People would spend their days on video conferences from the office working across the continents, adding to the frustration. Ironically, I had a remote exception, all while living in the middle of Manhattan, a five minute walk away from my old office where I had no team. Naturally, every junior IC was envious of my situation and found it completely unfair, which it was. <em>“What do you care? You have a remote exception.”</em>, two senior managers told me. Talk about empathy!</p>
<h3 id="my-decision-to-return">My Decision to Return</h3>
<p>To conclude, I had finally realized that my people skills were desperately needed while everyone was coming down from the post-pandemic roller coaster. And so, I became a manager again. I think I am capable of building great teams, as opposed to watching existing good ones burn out.</p>
<h3 id="pros-and-cons-of-being-a-manager-again">Pros and Cons of Being a Manager (Again)</h3>
<p>The cons of being a manager are obvious. It’s a lot more stressful. My team does all the work and I am not in full control of my outcomes, yet responsible for all the failures. The pros sum up to feeling more like a real human, seeing people grow, enjoying working on technology I care about, and witnessing success when enabling a team that does their best work.</p>
<h4 id="increased-multitasking-and-stress">Increased Multitasking and Stress</h4>
<p>This is one of the biggest cons. I do feel more stress as a manager because of the constant context switching, direct reports that routinely vent their personal life and frustrations with me, and exposure to more gossip and politics. Today I have 3 teams doing very different things at different stages of assembly, and have to stash and restore context in and out of my frontal lobe multiple times a day all while facing demanding senior leadership, and a lot of incoming pressures from multiple directions.</p>
<p>To mitigate this I try not to be anyone’s psychiatrist, and spend more time being the face of the organization. I force myself to work normal hours, protect time to focus on tasks that require flow, and force myself to write some code, and do other “real” work with my own hands. I also take ownership of problems visibly, and accept that being responsible for my team’s failures is simply my job.</p>
<h4 id="feeling-good-about-people-work">Feeling Good About People Work</h4>
<p>Whereas I had lots of 1:1s with my fellow Engineers, I was mostly tasked with producing software as an IC. As a manager, I am back to having to listen to people as part of my role. This forces me to take my 1:1s a lot more methodically, to followup on people problems, resolve conflicts, inspire each individual to be their best at work, and enable them in any way I can. As a consequence, it feels great to see everyone on a a team I built work well together and be inspired.</p>
<h4 id="working-with-people-rather-than-computers">Working with People Rather than Computers</h4>
<p>I still enjoy writing code after 35+ years of it, but I am learning less now, even though technology moves so fast and everything feels new again with A.I. A lot of the craft for an experienced Engineer can become a little boring, and I am not as motivated as someone who is in the middle of learning the exciting parts of it. While on the other hand, the younger people today are quite different from me, and surprise me often in more ways than none. Working with people as my primary job can be interesting in many new ways.</p>
<h4 id="feeling-control">Feeling Control</h4>
<p>As a senior manager I set direction and have significant amounts of organizational power. I know where the bodies are being buried because I hold the shovel. I make many decisions that affect others. I have access to detailed information, know early when someone important is quitting or being fired. I have a word in hiring, letting go and promoting. This comes with agonizing headaches, such as not being able to tell someone that they are about to lose their job, but there’s a better sense of control, and maybe even some level of psychological safety for myself. This is quite personal, but I do feel better when I sense I have a good grip on things.</p>
<h3 id="the-future-of-empire-building">The Future of Empire Building</h3>
<p>I don’t know how to conclude this post, so I will only say that now that I am a manager again, I might as well build an empire. I could divide and conquer my team into people I like, and people I don’t like, and terrorize the former. Of course, this is a joke, but I like to think about this empire-building concept that is often associated with senior managers as a signal. The moment I feel that I am, or have built an empire, is the moment to become an IC again, with <a href="/2019/11/17/the-pros-and-cons-of-going-from-management-back-to-ic.html">its own pros and cons</a>.</p>
<p><a href="https://code.dblock.org/2025/05/18/the-pros-and-cons-of-going-from-ic-back-to-management.html">Pros and Cons of Going from Individual Contributor Back to Manager</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on May 18, 2025.</p>https://code.dblock.org/2025/05/10/executing-structured-ai-workflows-with-shopify-roast2025-05-10T01:00:00+00:002025-05-10T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Last week, my team at Shopify released a new tool called <a href="https://github.com/Shopify/roast/">Roast</a>, a convention-oriented framework for creating structured AI workflows. Our vision is to enable A.I. agents to solve developer productivity problems at scale. Imagine continuously improving unit tests, or optimizing test performance across hundreds of thousands of tests with minimal human intervention!</p>
<p>As we embarked on this journey, we quickly learned that, like many junior Engineers, A.I. needs some help focusing, and may not be ready to roam free around millions of lines of code and not hallucinate random results. So we made Roast, which gave A.I. some structure.</p>
<p>In the following example I will grade a Ruby unit test.</p>
<p>If you don’t have one already, get an OpenAI key from <a href="https://platform.openai.com/settings/organization/api-keys">here</a>. You will need an account with a credit card, make sure that a basic completion works.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">OPENAI_API_KEY</span><span class="o">=</span>sk-proj-....
curl <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$API_TOKEN</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"What is 1+1?"}]}'</span> <span class="se">\</span>
https://api.openai.com/v1/chat/completions
</code></pre></div></div>
<p>This should produce a <code class="language-plaintext highlighter-rouge">chat.completion</code> response.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chatcmpl-BVdeKT88rtgxDCId9jFIpCr5sJQKv"</span><span class="p">,</span><span class="w">
</span><span class="nl">"object"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chat.completion"</span><span class="p">,</span><span class="w">
</span><span class="nl">"created"</span><span class="p">:</span><span class="w"> </span><span class="mi">1746880280</span><span class="p">,</span><span class="w">
</span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gpt-4.1-mini-2025-04-14"</span><span class="p">,</span><span class="w">
</span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Check out Roast. You can <code class="language-plaintext highlighter-rouge">gem install roast-ai</code> from <a href="https://rubygems.org/gems/roast-ai">Rubygems</a>, but I am going to reuse an example workflow, so <code class="language-plaintext highlighter-rouge">git clone [email protected]:Shopify/roast.git</code> and <code class="language-plaintext highlighter-rouge">cd roast</code>.</p>
<p>The <a href="https://github.com/Shopify/roast/blob/main/examples/grading/workflow.md">test grading workflow</a> is a senior software engineer and testing expert that evaluates the quality of a test based on guidelines. It’s configured to use <code class="language-plaintext highlighter-rouge">gpt-4.1-mini</code> to analyze coverage, but <code class="language-plaintext highlighter-rouge">o3</code>, a more expensive and capable model, to generate recommendations. The free OpenAI tier doesn’t have access to these, so I have edited the file to use <code class="language-plaintext highlighter-rouge">gpt-4.1-mini</code> everywhere (<code class="language-plaintext highlighter-rouge">model: gpt-4.1-mini</code>). I also told it to use my OpenAI token from <code class="language-plaintext highlighter-rouge">OPENAI_API_KEY</code> (<code class="language-plaintext highlighter-rouge">api_token: $(echo $OPENAI_API_KEY)</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diff --git a/examples/grading/workflow.yml b/examples/grading/workflow.yml
index d62a3d0..a10c7bc 100644
--- a/examples/grading/workflow.yml
+++ b/examples/grading/workflow.yml
@@ -1,5 +1,8 @@
name: Grading current test changes
+api_token: $(echo $OPENAI_API_KEY)
+model: gpt-4.1-mini
+
tools:
- Roast::Tools::Grep
- Roast::Tools::ReadFile
@@ -21,16 +24,13 @@ steps:
# set non-default attributes for steps below
analyze_coverage:
- model: gpt-4.1-mini
auto_loop: false
json: true
generate_grades:
- model: o3
json: true
generate_recommendations:
- model: o3
auto_loop: false
json: true
params:
</code></pre></div></div>
<p>You may also need <code class="language-plaintext highlighter-rouge">shadowenv</code> and <code class="language-plaintext highlighter-rouge">rg</code>, so <code class="language-plaintext highlighter-rouge">brew install shadowenv</code> and <code class="language-plaintext highlighter-rouge">brew install rg</code>.</p>
<p>Let’s run Roast on one of its own tests!</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/source/shopify/roast <span class="o">(</span>main<span class="o">)</span><span class="nv">$ </span>./exe/roast execute examples/grading/workflow.yml <span class="nb">test</span>/roast/resources_test.rb
🔥🔥🔥 Everyone loves a good roast 🔥🔥🔥
Configuring API client with token from workflow
Starting workflow...
Workflow: roast/examples/grading/workflow.yml
Options: <span class="o">{}</span>
Running workflow <span class="k">for </span>file: <span class="nb">test</span>/roast/resources_test.rb
Executing: read_dependencies <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
🔍 Searching <span class="k">for </span>file: resources.rb
📖 Reading file: lib/roast/resources.rb
Executing: run_coverage <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Requiring step file: roast/examples/grading/run_coverage.rb
Run options: <span class="nt">--seed</span> 19099
<span class="c"># Running:</span>
.............
Finished <span class="k">in </span>0.000992s, 13104.8373 runs/s, 16129.0306 assertions/s.
13 runs, 16 assertions, 0 failures, 0 errors, 0 skips
Executing: analyze_coverage <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Executing: verify_test_helpers <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Executing: verify_mocks_and_stubs <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
🔍 Grepping <span class="k">for </span>string: def
Executing: generate_grades <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Executing: calculate_final_grade <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Requiring step file: roast/examples/grading/calculate_final_grade.rb
Executing: format_result <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
Requiring step file: roast/examples/grading/format_result.rb
Executing: generate_recommendations <span class="o">(</span>Resource <span class="nb">type</span>: none<span class="o">)</span>
🔥🔥🔥 ROAST COMPLETE! 🔥🔥🔥
Final output saved to: roast/.roast/sessions/grading_current_test_changes/resources_test_rb_13224ed7/20250510_122040_774/final_output.txt
Final output saved to: roast/.roast/sessions/grading_current_test_changes/resources_test_rb_13224ed7/20250510_122040_774/final_output.txt
</code></pre></div></div>
<p>The test grade report is quite impressive.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>========== TEST GRADE REPORT ==========
Test file: test/roast/resources_test.rb
FINAL GRADE:
Score: 80/100
Letter Grade: B
RUBRIC SCORES:
Line Coverage (10% of grade):
Value: 9.0
Score: 9/10 - "The source file has 82.93% line coverage, which is good coverage with only minor or edge case code paths remaining untested."
Method Coverage (10% of grade):
Value: 10.0
Score: 10/10 - "The source file has 100% method coverage, indicating all methods are being tested."
Branch Coverage (30% of grade):
Value: 6.0
Score: 6/10 - "The source file has 62.96% branch coverage, indicating some conditions lack testing for all outcomes, especially in conditional branches related to resource type detection."
Test Helpers Usage (10% of grade):
Value: 10.0
Score: 10/10 - "No custom test helpers are used in the test file, so there are no issues related to helper usage."
Mocks and Stubs Usage (10% of grade):
Value: 10.0
Score: 10/10 - "No mocks or stubs are used in the test file, so there are no issues related to mocking or stubbing."
Test Readability (10% of grade):
Value: 8.0
Score: 8/10 - "Test names are descriptive and clearly communicate the expected behavior, though the structure could be improved with more context blocks or grouping."
Test Maintainability (10% of grade):
Value: 8.0
Score: 8/10 - "Tests focus on behavior and public interfaces without coupling to implementation details, making them reasonably maintainable."
Test Effectiveness (10% of grade):
Value: 7.0
Score: 7/10 - "Tests cover the main expected behaviors and resource types, but some edge cases and error conditions remain untested, as indicated by uncovered branches."
</code></pre></div></div>
<p>Using more expensive models takes a little longer to run and I get a “C” grade instead of a “B”. Static analysis, such as branch coverage, gets the same score, while test effectiveness goes from 7 to 6. The better model finds untested edge cases that may cause undetected regressions. Compare <em>“Tests cover the main expected behaviors and resource types, but some edge cases and branches remain untested, limiting full validation of all possible inputs.”</em> to <em>“Core behaviors (file, directory, url, api, none detection) are covered, but several branches - such as command targets and glob-pattern edge cases - remain untested, leaving room for undetected regressions.”</em>.</p>
<p>Honestly, wow.</p>
<p><a href="https://code.dblock.org/2025/05/10/executing-structured-ai-workflows-with-shopify-roast.html">Executing Structured A.I. Workflows with Shopify Roast</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on May 10, 2025.</p>https://code.dblock.org/2025/04/05/apologizing-for-my-obsessiveness-over-punctuation2025-04-05T01:00:00+00:002025-04-05T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>You may have landed here because I had just apologized for my obsessiveness over punctuation in a GitHub comment and requested changes to your pull request, or you noticed me adding periods to a sentence in the otherwise excellent document you have authored. This is not the first time, and my condition manifests itself in some interesting ways.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-04-05-my-punctuation-ocd/shoes.gif" /></p>
<p>Consider the following, seemingly well-formed sentence.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-04-05-my-punctuation-ocd/words-ok.png" /></p>
<p>This is what I actually see.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-04-05-my-punctuation-ocd/words-ocd.png" /></p>
<p>This must be, obviously, a <em>you</em> problem! How can you fix it? Add a period to this complete sentence.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-04-05-my-punctuation-ocd/words-fix.png" /></p>
<p>Quite seriously, the gist of the issue is that I am simply not capable of reading text that is not properly punctuated. It’s not you, it’s me. Please add a period.</p>
<p>My rule of thumb for dealing with this condition is that <em>a complete sentence should end with a period</em>. Don’t add a period to “the quick brown fox jumps over the lazy dog” or “The Quick Brown Fox Jumps Over The Lazy Dog”.</p>
<p>Consistently punctuate list items, such as either all items are full, capitalized sentences, or none of them are. The following are good examples.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. One quick brown fox jumps over the lazy dog.
2. Two quick brown foxes jump over the lazy dog.
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- one quick brown fox jumps over the lazy dog
- two quick brown foxes jump over the lazy dog
</code></pre></div></div>
<p>After a very deep breath I may be able to tolerate a period at the end of a list, as long as the items are capitalized consistently, but this is not recommended. Periods are cheap.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. One quick brown fox jumps over the lazy dog
2. Two quick brown foxes jump over the lazy dog
3. Three quick brown foxes jump over the lazy dog.
</code></pre></div></div>
<p>There’s “attention to detail” and there’s “obsessing over periods”. I am sorry for my obsession, but I do appreciate your attention to detail.</p>
<p>While you are here, you should also know that I am a compulsive sorter. For example, I organize my personal documents and photos by year, going back to 1990. Each year has a subfolder for each of the months.</p>
<p><img src="https://code.dblock.org/images/posts/2025/2025-04-05-my-punctuation-ocd/folders.gif" /></p>
<p>There’s more.</p>
<p>In 2016 I had to <a href="/2016/03/25/a-directory-structure-for-oss-and-work-github-clones.html">invent a structure for my local git clones</a> after having an episode. I have <a href="/2022/08/08/managing-github-notifications-in-gmail.html">hundreds of labels to sort through GitHub notifications</a>.</p>
<p><img src="https://code.dblock.org/images/posts/2022/2022-08-08-managing-github-notifications-in-gmail/labels.gif" /></p>
<p>The posts in this blog are <a href="https://github.com/dblock/code.dblock.org/tree/gh-pages/_posts">similarly organized</a>, and the fact that there’s both a <code class="language-plaintext highlighter-rouge">_posts</code> and a <code class="language-plaintext highlighter-rouge">posts</code> folder in Jekyll is currently very triggering. I am thrown off by lines <a href="/2021/06/07/to-wrap-or-not-to-wrap-in-markdown.html">wrapping in markdown</a>, and I wrote <a href="https://github.com/dblock/danger-changelog">a Danger plugin</a> to ensure consistent sentence formatting in a project’s CHANGELOG.md.</p>
<p>This condition spills into real life, as I obsessively classify the smallest of my expenses into dozens of custom categories in the personal finance app <a href="https://www.monarchmoney.com/referral?code=8k3crzojhz">Monarch</a>, including “Electric Scooter”, “Kids Lunch” and “Kitchen Utensils”.</p>
<p>While writing this post, I also realized that I am not as triggered by lack of order or punctuation in books. I quite enjoy the chaos of William S. Burroughs’ “Naked Lunch”. I wonder what it is about computers that causes this in me?</p>
<p><a href="https://code.dblock.org/2025/04/05/apologizing-for-my-obsessiveness-over-punctuation.html">Apologizing for My Obsessiveness Over Punctuation</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on April 05, 2025.</p>https://code.dblock.org/2025/02/10/leaving-amazon2025-02-10T01:00:00+00:002025-02-10T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Friday, February 14th is my last day at Amazon.</p>
<p>I’ve been at Amazon for over 5½ half years, and it’s been a great run. I came from <a href="/2019/05/01/reflecting-on-eight-years-building-artsy.html">8 years of leading technology at Artsy</a>, and wanted to go back to coding. I found a Principal Engineer role to help launch the new <a href="https://aws.amazon.com/data-exchange/">AWS Data Exchange</a> service. I was excited to work on a marketplace (<a href="https://artsy.net">Artsy</a> is a marketplace for fine art), and the idea of a service that could connect data providers and data consumers at Amazon scale was big. Plus, I was finally going to learn how Amazon, and especially AWS, was so relentlessly successful, from within.</p>
<p>I half-jokingly talked of this as semi-retirement, because being an individual contributor at a F.A.A.N.G. seemed a lot easier than being CTO of a company that had grown from seed stage to Series D, raising $100MM. It was certainly different. Six months into my IC role the <a href="/2019/11/13/aws-data-exchange.html">service had launched</a> and I <a href="/2019/11/17/the-pros-and-cons-of-going-from-management-back-to-ic.html">felt that I had made the right decision</a>. I didn’t write enough code though, and was missing working on open-source (Artsy was <a href="https://artsy.github.io/blog/2019/04/29/how-did-artsy-become-oss-by-default/">open-source by default</a>), so I moved to <a href="https://opensearch.org">OpenSearch</a>, a fork of Elasticsearch sponsored by Amazon, in 2021. That year was <a href="/2022/06/05/a-year-working-on-opensearch-2.0.html">incredibly exciting work</a>, getting the product off the ground, followed by steady growth, culminating with <a href="/2024/09/17/opensearch-joins-the-linux-foundation.html">OpenSearch Project joining the Linux Foundation</a> in 2024. In thinking about what to do next, I was not sure I could beat helping convince a 2.5 trillion dollar company to release control of a key project to a non-profit.</p>
<p>What have I learned over the years? If anything, it’s that Amazon is a unique company that is capable of changing its mind, but it’s not for the fainthearted. If you are considering a job there, I recommend it.</p>
<p>Allow me to share a few thoughts from my tenure.</p>
<ol>
<li>Nobody is irreplaceable, but people make all the difference.</li>
<li>A successful business is a simple one, and does what customers are asking, and willing to pay for.</li>
<li>Writing well is a superpower, and a useful tool for influencing people.</li>
<li>If you are passionate about what you’re building, it will not feel like work.</li>
<li>Most people will recognize and appreciate your passion, and maybe even give you the benefit of the doubt.</li>
<li>There is a time and place where criticism can bring value, be sure you know your intention or value before giving it, or risk losing trust.</li>
</ol>
<p>Finally, I’m forever grateful for my colleagues, the company leaders, and the Amazon PE community.</p>
<p>So, what’s next? We shall see! My criteria for a new role are as follows.</p>
<ol>
<li>I want to work for a company that has a high NPS score of both employee and customer happiness.</li>
<li>I want to continue increasing my time living outside of NYC, especially in Latin or South America, and therefore be able to do much of my focused work remotely.</li>
</ol>
<p>I recognize that I am privileged and extremely lucky to have opportunities where both of these can be met.</p>
<p>Onward!</p>
<p><a href="https://code.dblock.org/2025/02/10/leaving-amazon.html">Leaving Amazon</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on February 10, 2025.</p>https://code.dblock.org/2025/01/20/should-you-work-on-weekends2025-01-20T01:00:00+00:002025-01-20T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Should you work on week-ends?</p>
<p><em>tl;dr Only 2% of my paid work was accomplished on weekends, which matches my opinion that you should not.</em></p>
<h3 id="collecting-data">Collecting Data</h3>
<p>First, let’s try to use data and find out whether I actually work on week-ends. I’ve contributed to the open-source OpenSearch since 2021 as part of my day job, so I tried looking through my GitHub commits, but had hard time excluding forks. I settled on fetching pull requests. The following query collects my PRs for the first two weeks of 2025.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
gh search prs <span class="se">\</span>
<span class="nt">--author</span> <span class="s2">"@me"</span> <span class="se">\</span>
<span class="nt">--created</span> <span class="s2">"2025-01-01..2025-01-20"</span> <span class="se">\</span>
<span class="nt">--json</span> repository,number,title,createdAt <span class="se">\</span>
<span class="nt">--template</span> <span class="s1">'{{range .}}{{tablerow
(timefmt "Monday" .createdAt)
.repository.nameWithOwner
(printf "#%v" .number)
(truncate 20 .title)
(timeago .createdAt)
}}{{end}}'</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sunday dblock/discord-strava #32 Lock setup-firefox. 1 day ago
Friday dblock/discord-strava #30 Upgrade Firefox i... 3 days ago
Tuesday opensearch-project/opensearch-api-specification #793 Warn if file path... 6 days ago
Friday opensearch-project/opensearch-php #253 Added optional he... 10 days ago
Thursday opensearch-project/opensearch-php #249 Adding @kimpepper... 12 days ago
Wednesday dblock/code.dblock.org #139 Meeting Open Sour... 12 days ago
Wednesday opensearch-project/opensearch-php #247 Fix up UPGRADING.md. 12 days ago
Tuesday opensearch-project/opensearch-api-specification #772 Evaluate payload ... 13 days ago
Monday opensearch-project/opensearch-api-specification #768 Fix flaky snapsho... 14 days ago
Monday opensearch-project/opensearch-api-specification #767 Upload test spec ... 15 days ago
Wednesday dblock/code.dblock.org #137 Only vale modifie... 19 days ago
</code></pre></div></div>
<p>Repeating this pattern using different date ranges and a larger <code class="language-plaintext highlighter-rouge">--limit</code> I found 1,566 PRs, including 1,264 PRs into the opensearch-project GitHub organization since April 2021, the time of the OpenSearch fork. Excluding PRs made on Saturday and Sunday I got 1,234. It’s a suspicious number (1, 2, 3, 4, really?), but it checks out. The data says that I made 30 PRs on week-ends for work, and 225 PRs not for work. Finally, these exclude the many commits I made into my pet projects directly to <code class="language-plaintext highlighter-rouge">main</code>.</p>
<p>We can conclude that only 2% of my paid work was accomplished on weekends.</p>
<h3 id="which-day-is-more-productive">Which Day is More Productive?</h3>
<p>Here’s the distribution of PRs into opensearch-project (aka work) by day of week.</p>
<table>
<thead>
<tr>
<th>Day</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>Monday</td>
<td>221</td>
</tr>
<tr>
<td>Tuesday</td>
<td>331</td>
</tr>
<tr>
<td>Wednesday</td>
<td>193</td>
</tr>
<tr>
<td>Thursday</td>
<td>321</td>
</tr>
<tr>
<td>Friday</td>
<td>168</td>
</tr>
<tr>
<td>Saturday</td>
<td>17</td>
</tr>
<tr>
<td>Sunday</td>
<td>13</td>
</tr>
</tbody>
</table>
<p>It looks like Tuesdays and Thursdays are the busiest with fewer results on Friday, which is consistent with my opinion about the benefits of <em>finishing earlier or doing other types of work than code on Fridays</em>. Work is not just pull requests, it’s also attending meetings, and I <a href="/2024/12/20/i-participated-in-2882-meetings-in-5-years.html">participated in 2882 meetings in 5 years</a>.</p>
<h3 id="should-you-work-on-weekends">Should You Work on Weekends?</h3>
<p>In my opinion, an experienced Engineer or a People Manager working on weekends shows a bad example. It sets an unreasonable expectation that everyone must be available 7 days a week for productive work, reduces the overall velocity of a team by wearing people out, and diminishes the quality of the code we collectively produce. Us, humans, need to be able to step back away from the machine to recharge, and we need time to think, therefore working harder or more hours is usually not correlated with better results.</p>
<h3 id="what-about-looming-deadlines">What About Looming Deadlines?</h3>
<p>Over the years I observed that developers that bang out code on a Sunday tend to bang out a lot less code on Monday. Those on-call on weekends are exhausted the entire week. Individuals that can’t find time to focus on code during the work week tend to compensate by coding on weekends, which demonstrates that they are unproductive during the week - a net negative. Having to work on a weekend to meet a deadline is usually a failure of planning. There are certainly exceptions, but they should be exceedingly rare.</p>
<h3 id="what-about-pet-projects-and-open-source">What About Pet Projects and Open-Source?</h3>
<p>Pet projects and open-source contributions outside of paid work are a wonderful source of independence and dopamine, I have <a href="https://www.vestris.com/">several</a>. I like doing that type of work on weekends, but only for a small amount of hours, and never feeling that it takes away from my main week-end activities that include spending time with family or friends. I think this attitude evolved over the years as I used to spend a lot of time coding on week-ends, but I believe I have consistently tried to make sure that was not paid work that I <em>had to do</em>. Finally, I am lucky to be paid to do open-source work, but I absolutely consider that <em>work</em> and avoid doing it outside of work hours.</p>
<h3 id="creating-discipline">Creating Discipline</h3>
<p>Not working on weekends and not burning out requires discipline. If you find it difficult to detach yourself from the computer on weekends, begin your Saturday and Sunday with the hardest thing to do. For me, this meant forcing myself to open a book before my laptop, or putting my shoes on and going for a long run, first thing in the morning.</p>
<p>You can do it!</p>
<p><a href="https://code.dblock.org/2025/01/20/should-you-work-on-weekends.html">Should You Work on Weekends?</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on January 20, 2025.</p>https://code.dblock.org/2025/01/08/meeting-open-source-users-where-they-are2025-01-08T01:00:00+00:002025-01-08T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Most paying customers expect a certain level of (paid) support. One opens a ticket, gets a response, and any issue can be resolved.</p>
<p>How does “support” work in open-source? My rule of thumb is to meet users <em>where they are</em>.</p>
<p>GitHub projects have issues, but opening an issue to ask a question is generally frowned upon. Therefore, in JNA, we have a <a href="https://groups.google.com/g/jna-users">mailing list</a>, and in OpenSearch, we have <a href="https://forum.opensearch.org">a forum</a> and <a href="https://opensearch.org/slack.html">Slack</a>. StackOverflow is quite popular, too.</p>
<p>For a small project I choose <strong>GitHub issues</strong> as the preferred and only channel. It keeps things well organized and is the least amount of overhead for both users and maintainers. Most questions can be turned into either an issue (e.g. <em>“I have tried this, and did not get the expected results.”</em>), or a feature request (e.g. <em>“Would it be possible to add a feature that works as follows?”</em>). I like to use templates, such as <a href="https://github.com/opensearch-project/.github/tree/main/.github/ISSUE_TEMPLATE">these</a> to help guide users in opening issues with enough information to get meaningful help.</p>
<p>For a larger project that has multiple areas of interest I like <strong>Slack</strong> with dedicated channels for various areas, such as the <a href="https://opensearch.slack.com/archives/C051XNH62KB">#clients</a> channel on the OpenSearch public Slack. As a (co)maintainer of OpenSearch clients my responsibilities are as follows.</p>
<ol>
<li><strong>Create the channel</strong>. This is a public channel so that anyone in the workspace can join and participate, someone has to make one.</li>
<li><strong>Set channel purpose and topic</strong>. Clearly define the purpose and topic of the channel. For example, the purpose could be <em>“A space for discussing contributions to our project, including bug reports, feature requests, and code contributions.”</em> The topic can provide a brief overview of the contribution guidelines and processes.</li>
<li><strong>Pin important messages</strong>. Pin any important messages or documents related to contributing to the project, such as the contribution guidelines, code of conduct, or relevant links to your project’s repository or issue tracker.</li>
<li><strong>Encourage participation</strong>. Announce the new channel to community members in #general and encourage everyone to join and actively participate in the conversation. You can share the channel link or invite them directly.</li>
<li><strong>Moderate and respond</strong>. Monitor the channel regularly. Respond to questions, provide guidance, and ensure that discussions remain constructive and aligned with project’s contribution guidelines. I usually sweep my channels bi-weekly and respond to any message that has not been answered by someone else.</li>
<li><strong>Triage and assign issues</strong>. When contributors report bugs or suggest new features, ask them to create issues in project’s issue tracker, and share the links in the channel to close the loop. Work with the members of the channel to try and assign someone to investigate and address these issues when they are willing to help.</li>
<li><strong>Celebrate contributions</strong>. Recognize and celebrate contributions from community members. Share updates on merged pull requests, thank contributors, or highlight notable contributions in the channel.</li>
<li><strong>Provide feedback</strong>. Encourage contributors to share their experiences and provide feedback on the contribution process. Use this feedback to improve the guidelines, documentation, or processes as needed.</li>
<li><strong>Share updates and announcements</strong>. Use the channel to share releases, updates, announcements, or changes related to your project’s contribution processes or guidelines.</li>
</ol>
<p>An interesting aspect of the open-source OpenSearch is that it’s also offered as a paid managed service on AWS. Therefore, it’s common for an AWS customer to ask a question about using OpenSearch in the managed environment, which is mostly identical to using the open-source product. Many such customers are willing to do some work and will often help narrow the problem down by trying the open-source version. Such customers always appreciate the closer feedback loop of the developer community on Slack, and the ability to resolve their issues in code by themselves, which is another example of meeting your <del>users</del> customers where they are.</p>
<p><a href="https://code.dblock.org/2025/01/08/meeting-open-source-users-where-they-are.html">Meeting Open Source Users Where They Are</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on January 08, 2025.</p>https://code.dblock.org/2024/12/30/adding-a-spell-checker-to-a-jekyll-blog2024-12-30T01:00:00+00:002024-12-30T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>I found it annoyingly non-trivial to add a spell checker to this blog.</p>
<p>For now, I settled on <a href="https://github.com/marketplace/actions/github-spellcheck-action#extra-configuration-for-markdown">GitHub Spellcheck Action</a> that uses <a href="https://facelessuser.github.io/pyspelling">PySpelling</a> on files changed in the commit or pull request as described in <a href="https://swild.dev/self-hosting/github-spellcheck-lint-action/">this blog post</a>.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Check Spelling</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">,</span> <span class="nv">pull_request</span><span class="pi">]</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">check</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">tj-actions/[email protected]</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">changed_files</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">files</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">**/**.md</span>
<span class="s">**/**.markdown</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check Spelling</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">rojopolis/[email protected]</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">task_name</span><span class="pi">:</span> <span class="s">Markdown</span>
<span class="na">config_path</span><span class="pi">:</span> <span class="s">.pyspelling.yml</span>
<span class="na">source_files</span><span class="pi">:</span> <span class="s">${{ steps.changed_files.outputs.all_changed_files }}</span>
</code></pre></div></div>
<p>To run PySpelling locally ensure you have a working version of Python, install PySpelling with <code class="language-plaintext highlighter-rouge">pip install pyspelling</code>, and <a href="http://aspell.net/">aspell</a> with <code class="language-plaintext highlighter-rouge">brew install aspell</code> on a Mac. In <a href="https://github.com/dblock/code.dblock.org/blob/gh-pages/.pyspelling.yml">my configuration</a> I also use <code class="language-plaintext highlighter-rouge">pymdownx</code> from <a href="https://facelessuser.github.io/pymdown-extensions">pymdown-extensions</a> which is installed with <code class="language-plaintext highlighter-rouge">pip install pymdown-extensions</code>.</p>
<p>You need a <a href="https://github.com/dblock/code.dblock.org/blob/gh-pages/.pyspelling.yml">.pyspelling.yml</a> and you can run it as follows.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pyspelling <span class="nt">--config</span> .pyspelling.yml
</code></pre></div></div>
<p>This is a Jekyll blog in which we want to ignore code, wrapped between Jekyll magic commands for syntax highlighting. This can be accomplished with a PySpelling pipeline in the above-mentioned configuration file.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">pipeline</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">pyspelling.filters.context</span><span class="pi">:</span>
<span class="na">context_visible_first</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">delimiters</span><span class="pi">:</span>
<span class="c1"># ignore jekyll multiline magic highlights {% ... %}</span>
<span class="pi">-</span> <span class="na">open</span><span class="pi">:</span> <span class="s1">'</span><span class="s">(?s)^\{\%</span><span class="nv"> </span><span class="s">highlight</span><span class="nv"> </span><span class="s">.*</span><span class="nv"> </span><span class="s">\%\}$'</span>
<span class="na">close</span><span class="pi">:</span> <span class="s1">'</span><span class="s">^\{\%</span><span class="nv"> </span><span class="s">endhighlight</span><span class="nv"> </span><span class="s">\%\}$'</span>
<span class="c1"># ignore the rest of jekyll magic commands</span>
<span class="pi">-</span> <span class="na">open</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{%'</span>
<span class="na">close</span><span class="pi">:</span> <span class="s1">'</span><span class="s">%}'</span>
</code></pre></div></div>
<p>Finally, we can collect the initial set of words to potentially exclude from existing posts into <a href="https://github.com/dblock/code.dblock.org/blob/gh-pages/.pyspelling.words">.pyspelling.words</a>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pyspelling <span class="nt">--config</span> .pyspelling.yml | <span class="se">\</span>
<span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'/^Misspelled words:$/,/^-*$/!p'</span> | <span class="se">\</span>
<span class="nb">grep</span> <span class="nt">-v</span> <span class="s2">"^---</span><span class="se">\+</span><span class="s2">$"</span> | <span class="se">\</span>
<span class="nb">grep</span> <span class="nt">-v</span> <span class="s2">"^$"</span> | <span class="se">\</span>
<span class="nb">sort</span> | <span class="se">\</span>
<span class="nb">uniq</span> <span class="o">></span> .pyspelling.words
</code></pre></div></div>
<p>The file helped me spot a few spelling mistakes, now fixed. See <a href="https://github.com/dblock/code.dblock.org/pull/134">code.dblock.org#134</a> for the full change.</p>
<p>Update: I eventually switched to using regular backticks for code when adding a style checker in <a href="https://github.com/dblock/code.dblock.org/pull/136">#136</a>, so the above <code class="language-plaintext highlighter-rouge">delimiters</code> rule changed as follows.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">pyspelling.filters.context</span><span class="pi">:</span>
<span class="na">context_visible_first</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">escapes</span><span class="pi">:</span> <span class="s">\\[\\`~]</span>
<span class="na">delimiters</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">open</span><span class="pi">:</span> <span class="s2">"</span><span class="s">(?s)^(?P<open></span><span class="nv"> </span><span class="s">*`{3,})([a-z+]*)$"</span>
<span class="na">close</span><span class="pi">:</span> <span class="s2">"</span><span class="s">^(?P=open)$"</span>
<span class="pi">-</span> <span class="na">open</span><span class="pi">:</span> <span class="s2">"</span><span class="s">(?P<open>`+)"</span>
<span class="na">close</span><span class="pi">:</span> <span class="s2">"</span><span class="s">(?P=open)"</span>
<span class="pi">-</span> <span class="na">open</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{%'</span>
<span class="na">close</span><span class="pi">:</span> <span class="s1">'</span><span class="s">%}'</span>
</code></pre></div></div>
<p><a href="https://code.dblock.org/2024/12/30/adding-a-spell-checker-to-a-jekyll-blog.html">Adding a Spell Checker to a Jekyll Blog</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on December 30, 2024.</p>https://code.dblock.org/2024/12/20/i-participated-in-2882-meetings-in-5-years2024-12-20T01:00:00+00:002024-12-20T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Since my first day in AWS 5 1/2 years ago, I’ve been experimenting with keeping a CHANGELOG of everything I do, available for everyone at the company to see. I wrote about it <a href="/2020/09/01/keep-a-changelog-at-work.html">here</a>. If you work at AWS, use the URL in the screenshot below to find it.</p>
<p><img src="/images/posts/2020/2020-09-01-keep-a-changelog-at-work/onboarding.png" alt="" /></p>
<p>I wrote a little script to try and classify where I spent my time. Here are some quick, very unscientific results.</p>
<p>I participated in <strong>2882 meetings</strong>, 75% were probably technical discussions and 25% were not, including business reviews. I made <strong>1013</strong> code contributions, reviewed or read <strong>364</strong> documents, mostly technical designs. I did <strong>192 1:1s</strong> with my colleagues, <strong>130</strong> interviews, and helped with <strong>80 promos</strong>. I debugged or otherwise dealt with <strong>175 customer-facing issues</strong>, authored <strong>78 documents</strong>, took <strong>64 trainings</strong>, and engaged in <strong>48</strong> planning exercises.</p>
<p>This, of course, doesn’t tell you anything useful. If you actually want to learn more about what PEs do at Amazon, check out the excellent <a href="https://www.linkedin.com/pulse/principal-engineer-roles-framework-mai-lan-tomsen-bukovec-142df/">Principal Engineer Roles Framework</a> that I have been using since 2019 and that was made public this week by Mai-Lan Tomsen Bukovec.</p>
<p><a href="https://code.dblock.org/2024/12/20/i-participated-in-2882-meetings-in-5-years.html">I Participated in 2882 Meetings in 5 Years</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on December 20, 2024.</p>https://code.dblock.org/2024/12/19/do-not-fix-bugs-reported-in-your-open-source-projects2024-12-19T01:00:00+00:002024-12-19T01:00:00+00:00Daniel Doubrovkinehttps://code.dblock.org[email protected]<p>Have you watched <a href="https://en.wikipedia.org/wiki/The_IT_Crowd">The IT Crowd</a>? It’s a hilarious British television sitcom from around 2006 that cast a bunch of IT geniuses at the Reynholm Industries tech support department in London. One of the signature laughs is that every time the phone rang, Roy would pick it up and without waiting say “Have you turned it off and on again?”, then hang up. I often feel like Roy when engaging with users reporting bugs in open-source projects I maintain.</p>
<p>Here’s my structured approach for any bug being reported in my open-source projects.</p>
<ol>
<li>Do not fix the bug. Because this is open source, and we are not selling software, maintainers may have some social responsibility, but are under no obligation to do anything.</li>
<li>Do not try to reproduce the bug. Not convinced it’s a bug? Cannot reproduce the issue? Politely request additional information or clarification.</li>
<li>Do not try to write a test that proves this is a bug. Ask the person reporting the issue to try and write the automated and failing test. This helps narrow down the problem and ensures that the bug stays fixed in the future.</li>
<li>Do not fix the bug. Now that the person reporting the bug has written an automated test for it, they are <em>this</em> close from fixing it. Ask them to do it. This gives the individual reporting the bug a sense of ownership and contribution to the project.</li>
<li>Do not do anything else. Can’t get any engagement on the bug from the person reporting it? Leave the bug open. Someone else will pick it up.</li>
</ol>
<p>tl;dr Do not fix the bug! A healthy open-source project will have lots of engaged contributors, especially when it comes to bugs. This is one of the <a href="/2024/12/18/harvesting-the-low-hanging-fruit-in-open-source-projects.html">lowest hanging fruit</a> that you can harvest as a maintainer. That said, sometimes I just want to fix the bug myself because it’s so interesting. So YMMV.</p>
<p><a href="https://code.dblock.org/2024/12/19/do-not-fix-bugs-reported-in-your-open-source-projects.html">Do Not Fix Bugs Reported in Your Open Source Projects</a> was originally published by Daniel Doubrovkine at <a href="https://code.dblock.org">code.dblock.org | tech blog</a> on December 19, 2024.</p>