<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Charles Ouimet</title>
    <description>The latest articles on DEV Community by Charles Ouimet (@couimet).</description>
    <link>https://dev.to/couimet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1475576%2Fd36deeb8-78e0-4310-9748-efdf225ed9e9.jpeg</url>
      <title>DEV Community: Charles Ouimet</title>
      <link>https://dev.to/couimet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/couimet"/>
    <language>en</language>
    <item>
      <title>RangeLink 2.0.0: Bind first. Every R-* follows</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Sat, 13 Jun 2026 00:37:35 +0000</pubDate>
      <link>https://dev.to/couimet/rangelink-200-bind-first-every-r-follows-11nd</link>
      <guid>https://dev.to/couimet/rangelink-200-bind-first-every-r-follows-11nd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vckvmiq54wa61e8u3g9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vckvmiq54wa61e8u3g9.png" alt="RangeLink Logo" width="256" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey folks! Yes, it's been a while. RangeLink v1.0.0 shipped in mid-December 2025, and here we are in mid-June with v2.0.0 finally out the door.&lt;/p&gt;

&lt;p&gt;If you were wondering whether the project went quiet, it didn't. It got busy under the hood. In v1.0.0, destinations were the place R-L sent your link. In v2.0.0, destinations carry the whole keybinding family: the new R-F and R-G route through your destination, and R-V now sends terminal selections too. A richer right-click layer sits on top of all of it. And while we were in there, we stopped silently overwriting your system clipboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's New in v2.0.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Every R-* now routes through your destination
&lt;/h3&gt;

&lt;p&gt;In v1.0.0, R-L was the star and R-V had just landed. R-L was destination-agnostic: it dropped the link on the clipboard whether or not you had a destination bound. In v2.0.0, every send-style R-* command (R-L, R-V, and R-F) sends to your bound destination. If nothing is bound yet, the destination picker opens and the operation finishes against whatever you pick. Right-click context menu entries work the same way, so you don't have to memorise a keybinding to get the same flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R-V grew up.&lt;/strong&gt; It used to send editor selections to your bound destination. Now it also sends &lt;em&gt;terminal&lt;/em&gt; selections. Highlight some shell output, press R-V, and it goes straight to your bound destination. No more clipboard juggling to get test output into a prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R-F: Send File Path.&lt;/strong&gt; &lt;code&gt;R-F&lt;/code&gt; sends the current file's workspace-relative path to your bound destination. &lt;code&gt;R-shift-F&lt;/code&gt; sends the absolute path. Tell your AI assistant "look at this file" without typing the path or dragging the tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R-G: Go to Link.&lt;/strong&gt; &lt;code&gt;R-G&lt;/code&gt; opens a prompt; paste any RangeLink (something like &lt;code&gt;recipes/baking/chickenpie.ts#L3C14-L15C9&lt;/code&gt;) and you land at that exact spot. Useful for jumping to RangeLinks your AI hands back.&lt;/p&gt;

&lt;h3&gt;
  
  
  We stopped messing with your clipboard
&lt;/h3&gt;

&lt;p&gt;Every prior version of RangeLink wrote to your system clipboard as a side effect of nearly every operation. Even when the clipboard was just the transport to a destination, whatever you had copied before was gone. Slack message in your clipboard and you press R-L? Replaced. Text you were about to paste somewhere else? Gone.&lt;/p&gt;

&lt;p&gt;v2.0.0 introduces the &lt;code&gt;rangelink.clipboard.preserve&lt;/code&gt; setting (default &lt;code&gt;"always"&lt;/code&gt;). RangeLink now snapshots your clipboard before each transport operation and restores it afterward. Your clipboard only changes when &lt;em&gt;you&lt;/em&gt; ask for it: R-C or R-shift-C. If you preferred the old behaviour, set the preference to &lt;code&gt;"never"&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One picker, one menu: R-D and R-M
&lt;/h3&gt;

&lt;p&gt;In v1.0.0, binding meant running a separate command for each destination type ("Bind to Terminal," "Bind to Claude Code," "Bind to Cursor AI").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R-D: Bind to Destination.&lt;/strong&gt; One keybinding opens a single picker showing every available destination: AI assistants (built-in and any custom ones you've configured), terminals, and open files. AI assistants appear in a fixed order. Terminals and open files follow — if one of those is bound, it's pinned to the top of its section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a7kmra05v6mp2ovm8r1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a7kmra05v6mp2ovm8r1.png" alt="Bind to destination" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R-M: RangeLink Menu.&lt;/strong&gt; Groups jump-to-bound, unbind, go-to-link, and show-version into one menu. Open the menu from the keybinding or by clicking the RangeLink item in the status bar. That status bar item now reflects your bind state: prominent colour when bound, with a tooltip naming the destination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypgob01gdmz1dyg5714o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypgob01gdmz1dyg5714o.png" alt="RangeLink menu" width="799" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't want to memorize both R-D and R-M? R-M alone gets you far — when nothing is bound, it also lists available destinations so you can bind right from the menu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bring Your Own AI
&lt;/h3&gt;

&lt;p&gt;RangeLink already had &lt;strong&gt;BYOD&lt;/strong&gt; (Bring Your Own Delimiters) for cross-config link compatibility. v2.0.0 adds &lt;strong&gt;BYOA&lt;/strong&gt; (Bring Your Own AI): a universal connector that turns any focus-friendly VS Code or Cursor extension into a RangeLink destination.&lt;/p&gt;

&lt;p&gt;A new &lt;code&gt;rangelink.customAiAssistants&lt;/code&gt; setting lets you point RangeLink at any such extension. You name the extension, list its commands, and RangeLink wires it into the R-D picker. The setting supports three command tiers: direct text insert, focus plus auto-paste, or focus plus manual paste. The configured assistants are shape-validated at startup; the first time you use one, RangeLink picks the highest-priority tier whose commands are actually registered in your session.&lt;/p&gt;

&lt;p&gt;A minimal config for a hypothetical extension &lt;code&gt;acme.powerful-ai-extension&lt;/code&gt; that exposes a direct-insert command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"rangelink.customAiAssistants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extensionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acme.powerful-ai-extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extensionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Powerful AI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"insertCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"powerfulAi.insertText"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the extension exposes a focus-and-paste command instead of a direct insert, swap &lt;code&gt;insertCommands&lt;/code&gt; for &lt;code&gt;focusAndPasteCommands&lt;/code&gt;. If it only exposes a focus command, use &lt;code&gt;focusCommands&lt;/code&gt; and RangeLink will handle the paste step itself. You can list all three on the same entry as fallbacks; RangeLink picks the highest tier whose commands are actually registered.&lt;/p&gt;

&lt;p&gt;The same setting lets you override the built-in mappings for Claude Code, Gemini, Cursor AI, or Copilot. That matters less for the rare command rename and more for the case where an extension ships a new command RangeLink doesn't know about yet. You don't have to wait for a RangeLink release to start using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini Code Assist, built in
&lt;/h3&gt;

&lt;p&gt;The fourth built-in lands too. The full built-in lineup is now Claude Code, Gemini Code Assist, Cursor AI, and GitHub Copilot Chat. No setup beyond installing the Gemini extension itself, and it shows up in the R-D picker alongside the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Right-click works everywhere now
&lt;/h3&gt;

&lt;p&gt;New context menus in Explorer, Editor Tab, Editor Content, Terminal Tab, and Terminal Content. Each menu shows the actions that make sense for what you right-clicked: Send RangeLink, Send Portable Link, Send Selected Text, Send File Path, Bind Here, Unbind.&lt;/p&gt;

&lt;p&gt;One worth singling out: &lt;strong&gt;right-click any file in the Explorer&lt;/strong&gt;, pick "Send Relative File Path" (or "Send File Path" for the absolute version), and it goes straight to your bound destination. The file doesn't have to be open. R-F covers the current file; the Explorer entry covers &lt;em&gt;any&lt;/em&gt; file in your workspace. Bind RangeLink to your preferred AI tool and you can dump file paths into your prompt about as fast as you can right-click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggngspjpkyjjq6stv0wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggngspjpkyjjq6stv0wp.png" alt="Explorer right-click menu" width="620" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You'll know when there's a new version
&lt;/h3&gt;

&lt;p&gt;VS Code itself shows a "What's New" tab for editor releases, but extensions auto-update silently in the background. RangeLink v2.0.0 closes that gap with a once-per-upgrade toast: "What's New" opens the release notes, "Skip for this version" silences it for the current release. Dismiss without acting and it quietly reappears next time the extension activates, until you actually click one of the buttons. No nagging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The R-Keybinding Family in v2.0.0
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keybinding&lt;/th&gt;
&lt;th&gt;Letter&lt;/th&gt;
&lt;th&gt;Uses bound destination&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-L&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;L&lt;/strong&gt; for &lt;strong&gt;L&lt;/strong&gt;ink&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Generate a RangeLink at the current selection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;V&lt;/strong&gt; for paste&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Send selected text from editor or terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-F&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;F&lt;/strong&gt; for &lt;strong&gt;F&lt;/strong&gt;ile&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Send the current file's path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;C&lt;/strong&gt; for &lt;strong&gt;C&lt;/strong&gt;lipboard&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Copy a RangeLink to clipboard only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-D&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;D&lt;/strong&gt; for &lt;strong&gt;D&lt;/strong&gt;estination&lt;/td&gt;
&lt;td&gt;binds it&lt;/td&gt;
&lt;td&gt;Open the destination picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;M&lt;/strong&gt; for &lt;strong&gt;M&lt;/strong&gt;enu&lt;/td&gt;
&lt;td&gt;acts on it&lt;/td&gt;
&lt;td&gt;Open the RangeLink menu (jump, unbind, go to link, version)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-J&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;J&lt;/strong&gt; for &lt;strong&gt;J&lt;/strong&gt;ump&lt;/td&gt;
&lt;td&gt;focuses it&lt;/td&gt;
&lt;td&gt;Focus your currently bound destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-G&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;G&lt;/strong&gt; for &lt;strong&gt;G&lt;/strong&gt;o to link&lt;/td&gt;
&lt;td&gt;independent&lt;/td&gt;
&lt;td&gt;Paste a RangeLink and jump straight to it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-U&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;U&lt;/strong&gt; for &lt;strong&gt;U&lt;/strong&gt;nbind&lt;/td&gt;
&lt;td&gt;unbinds it&lt;/td&gt;
&lt;td&gt;Unbind the current destination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Upgrading from v1.0.0
&lt;/h2&gt;

&lt;p&gt;A few things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your clipboard is no longer collateral damage.&lt;/strong&gt; Anything you copied before pressing R-L, R-F, or R-V is still there afterwards. Set &lt;code&gt;rangelink.clipboard.preserve&lt;/code&gt; to &lt;code&gt;"never"&lt;/code&gt; if you preferred the old behaviour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every R-* command now routes through your bound destination.&lt;/strong&gt; R-L, R-V, and R-F all send to whatever you've bound. If nothing is bound when you press one, RangeLink prompts you to pick a destination instead of writing silently to the clipboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binding gets a keybinding: R-D.&lt;/strong&gt; In v1.0.0, binding was only available through the command palette. R-D opens a unified destination picker so you can bind without hunting through menus.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS Code: &lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cursor: &lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Project home: &lt;a href="https://ouimet.info/projects/rangelink-extension.html" rel="noopener noreferrer"&gt;ouimet.info/projects/rangelink-extension.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick start:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press R-D and pick a destination from the list&lt;/li&gt;
&lt;li&gt;Select some code, press R-L, and your RangeLink lands in the destination&lt;/li&gt;
&lt;li&gt;Press R-F to send the current file's path&lt;/li&gt;
&lt;li&gt;Highlight some terminal output and press R-V to send it&lt;/li&gt;
&lt;li&gt;Right-click any file in the Explorer and pick "Send Relative File Path" (no need to open it first)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  So why did it take six months?
&lt;/h2&gt;

&lt;p&gt;Every feature above touches foundational layers. R-D pulled the separate bind commands into a single picker and made every R-* (except R-C) demand a bound destination instead of falling through to the clipboard. BYOA changed the command-dispatch path that most of the existing tests already ran through, which meant a lot of assertions had to be retraced. And clipboard preservation touched every R-* command that used the clipboard as a transport step. The unit tests that carried v1.0.0 were solid, but they were not going to catch a regression in any of those.&lt;/p&gt;

&lt;p&gt;So I built a real integration-test harness first. Over 290 integration tests across more than 30 suite files, all running inside an actual VS Code host with live terminals, editor groups in different layouts, and AI extensions pre-installed for the runner (Claude Code, Gemini Code Assist). Roughly two-thirds are fully automated, another third are human-in-the-loop "assisted" tests for flows VS Code's automation API can't drive on its own, and a handful are still manual.&lt;/p&gt;

&lt;p&gt;It's the testing harness I wish I'd had when v1.0.0 shipped. It's also the reason I kept pushing the release date out: I wanted to be able to look at the v2.0.0 release notes and trust them.&lt;/p&gt;

&lt;p&gt;That's what I've been cooking. It's finally yours.&lt;/p&gt;




&lt;h2&gt;
  
  
  Happy (Range) Linking!
&lt;/h2&gt;

&lt;p&gt;If RangeLink is useful to you, star the &lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; and report issues or ideas via &lt;a href="https://github.com/couimet/rangeLink/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ouimet.info/projects/rangelink-extension.html" rel="noopener noreferrer"&gt;Project Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink/tree/main/packages/rangelink-vscode-extension/CHANGELOG.md#200" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;About the author&lt;/strong&gt; — &lt;a href="https://ouimet.info" rel="noopener noreferrer"&gt;Charles Ouimet&lt;/a&gt; is a Principal Software Developer in Montréal. He builds tools like RangeLink to fix the annoyances he runs into himself, balancing distributed systems by day with questionable side-project decisions by night. If something here helped you, coffee's always appreciated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/couimet" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Forange_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vscode</category>
      <category>productivity</category>
      <category>rangelink</category>
    </item>
    <item>
      <title>Correlation ID vs Request ID: A Practical Guide</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Wed, 10 Jun 2026 02:00:42 +0000</pubDate>
      <link>https://dev.to/couimet/correlation-id-vs-request-id-a-practical-guide-2l6o</link>
      <guid>https://dev.to/couimet/correlation-id-vs-request-id-a-practical-guide-2l6o</guid>
      <description>&lt;p&gt;A request hits your API. Your service calls two others. One of them calls two more. The first downstream call times out, so you retry. Something fails. You open the logs.&lt;/p&gt;

&lt;p&gt;Can you trace the full journey end-to-end? Can you tell which entries are the retry?&lt;/p&gt;

&lt;p&gt;Two identifiers answer these questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correlation ID&lt;/strong&gt; links everything across an end-to-end flow. It stays the same from the first inbound request through every downstream call. &lt;strong&gt;Request ID&lt;/strong&gt; identifies a single hop. Each outbound call gets its own. Retry a call, and you get the same correlation ID under a new request ID.&lt;/p&gt;

&lt;p&gt;They complement each other, and understanding the distinction is the difference between logs that help and logs that taunt.&lt;/p&gt;

&lt;p&gt;I've built this pattern more than once. Each time, I went looking for one good article to send the team and came up short -- every piece I liked either stayed in reference-doc weeds or pivoted straight to OpenTelemetry traces. None reached for the analogy that made it click for me. So I wrote the one I wish I'd had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The kitchen
&lt;/h2&gt;

&lt;p&gt;Picture a kitchen brigade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chef&lt;/strong&gt; -- gets the ticket, calls SousChef and LineCook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SousChef&lt;/strong&gt; -- handles recipes, delegates to PrepCook and DishWasher&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LineCook&lt;/strong&gt; -- executes the dish&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PrepCook&lt;/strong&gt; -- prepares ingredients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DishWasher&lt;/strong&gt; -- provides clean plates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wxyp0hip7o6il7k8pyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wxyp0hip7o6il7k8pyh.png" alt="Kitchen brigade" width="409" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The ticket and the shout
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;table ticket&lt;/strong&gt; is your correlation ID. Table 4's ticket stays pinned to the rail from the moment the waiter clips it there until the dish goes out. Every station reads the same ticket number. PrepCook knows it's for table 4. LineCook knows it's for table 4. Nobody asks &lt;em&gt;which table is this for?&lt;/em&gt; because the ticket never changes hands.&lt;/p&gt;

&lt;p&gt;Each &lt;strong&gt;shout&lt;/strong&gt; between stations is a request ID. When Chef shouts "Fire table 4, two salmon!", that shout gets its own identifier. If LineCook doesn't respond, Chef shouts again -- same ticket, new shout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mise en place
&lt;/h2&gt;

&lt;p&gt;In a kitchen, mise en place means prepping and organizing every ingredient before the first pan hits the flame. When service starts, you reach for what you need without thinking. Our code needs the same.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CorrelationId&lt;/code&gt; and &lt;code&gt;RequestId&lt;/code&gt; are &lt;a href="https://vimeo.com/13549100" rel="noopener noreferrer"&gt;Value Objects&lt;/a&gt;: tiny, immutable wrappers. Pass a ticket ID where a shout ID belongs and the compiler stops you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CorrelationId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;CorrelationId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;CorrelationId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;RequestId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RequestId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other half of mise en place is making these available everywhere without passing them around. The middleware at the boundary pushes them into two places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async-local context&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/@opentelemetry/context-async-hooks" rel="noopener noreferrer"&gt;OpenTelemetry's &lt;code&gt;AsyncLocalStorageContextManager&lt;/code&gt;&lt;/a&gt; wraps execution so &lt;code&gt;ExecutionContext.correlationId&lt;/code&gt; and &lt;code&gt;ExecutionContext.requestId&lt;/code&gt; behave like globals within the async call chain. (It's the modern successor to &lt;code&gt;AsyncHooksContextManager&lt;/code&gt; and requires a recent Node runtime.) At the boundary, a call to &lt;code&gt;ExecutionContext.run(...)&lt;/code&gt; populates the context; any code downstream reads from it without needing the IDs passed explicitly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The logging tooling&lt;/strong&gt;: The logger is &lt;code&gt;ExecutionContext&lt;/code&gt;-aware: every log entry automatically carries &lt;code&gt;correlationId&lt;/code&gt; and &lt;code&gt;requestId&lt;/code&gt;. In the examples below, &lt;code&gt;log.info()&lt;/code&gt; only passes extra context like &lt;code&gt;fn&lt;/code&gt; or &lt;code&gt;url&lt;/code&gt; -- the core IDs are added automatically, as the inline comment notes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Business code rarely touches &lt;code&gt;ExecutionContext&lt;/code&gt; directly. It just logs what it's doing. The IDs are present on every entry, searchable and filterable, without a single explicit reference in the business logic.&lt;/p&gt;

&lt;p&gt;The examples below use UUID v4. Whether that's the right format for your system is a separate question — &lt;a href="https://hilton.org.uk/blog/microservices-correlation-id" rel="noopener noreferrer"&gt;Hilton's article on microservice correlation IDs&lt;/a&gt; covers the tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tickets from the front of house (your public API)
&lt;/h2&gt;

&lt;p&gt;A request lands at your public API. You generate both IDs yourself. Accepting an external caller's correlation ID or request ID means relaying uncontrolled data into your internal logs. A buggy client sending &lt;code&gt;x-correlation-id: foo&lt;/code&gt; for every request pollutes every downstream service's logs with the same value. Same goes for &lt;code&gt;x-request-id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Public API middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;externalCorrelationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-correlation-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// remembered, not used&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;externalRequestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// remembered, not used&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// our table ticket&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// our shout&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-correlation-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;externalCorrelationId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// echo back theirs&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;externalRequestId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// correlationId and requestId are automatically added to every log entry by the logging tooling&lt;/span&gt;
    &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;publicApiMiddleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Echoing the caller's original IDs back on the response is a nicety: they get their IDs back for their own tracing, but your logs stay clean. You can also add &lt;code&gt;x-correlation-id-internal&lt;/code&gt; and &lt;code&gt;x-request-id-internal&lt;/code&gt; response headers with the IDs you used internally so callers can reference them when reporting issues. The tradeoff: this exposes internal identifiers, which some organizations consider a data leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your own crew (your internal API)
&lt;/h2&gt;

&lt;p&gt;When all your callers are your own kitchen stations, accepting their request ID lets them grep your logs when SousChef asks why the steak showed up instead of the salmon (debugging a failed request), when the manager wants to know why the salmon went out cold (latency investigation), or when the health inspector wants the full ticket timeline (audit).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Internal API middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/internal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-correlation-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// trust the crew's shout&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-correlation-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// correlationId and requestId are automatically added to every log entry by the logging tooling&lt;/span&gt;
    &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;internalApiMiddleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both middlewares fit in the same app, each scoped to its own route group. The boundary between internal and external is already in your route registration.&lt;/p&gt;

&lt;p&gt;The table ticket (correlation ID) is the cross-station link. The trust decision is structural -- once you've drawn the line in your route registration, you don't make it again at every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shouts across the stations (your internal outbound calls)
&lt;/h2&gt;

&lt;p&gt;Every time Chef calls out to another station, the table ticket (correlation ID) gets forwarded but the shout (request ID) is always new -- generated &lt;em&gt;before&lt;/em&gt; the call goes out so you have an ID to log against even if the call times out. Because &lt;code&gt;ExecutionContext.run(...)&lt;/code&gt; was called at the inbound boundary, the correlation ID is globally available -- the interceptor reads it without needing &lt;code&gt;req&lt;/code&gt; in scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Axios outbound interceptor&lt;/span&gt;
&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;CorrelationId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// same table ticket, or a fresh one if no inbound context&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outboundRequestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// new shout, local to this hop&lt;/span&gt;

  &lt;span class="c1"&gt;// The auto-added requestId is still the INBOUND one (the logger pulls from&lt;/span&gt;
  &lt;span class="c1"&gt;// ExecutionContext); log outboundRequestId explicitly so this hop is traceable.&lt;/span&gt;
  &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outboundInterceptor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outboundRequestId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outbound call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-correlation-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outboundRequestId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subtle thing about the log line above: &lt;code&gt;log.info()&lt;/code&gt; auto-injects the &lt;code&gt;requestId&lt;/code&gt; from &lt;code&gt;ExecutionContext&lt;/code&gt;, which is still the &lt;strong&gt;inbound&lt;/strong&gt; request ID (the inbound call hasn't finished yet -- we're mid-flight). The new outbound ID lives only in this function's local scope, so passing it as &lt;code&gt;requestId&lt;/code&gt; would get overridden by the logger's auto-injection. Log it under a different key (&lt;code&gt;outboundRequestId&lt;/code&gt; above) and it sits alongside the auto-injected fields without colliding. From the downstream service's perspective the same ID lands as &lt;code&gt;x-request-id&lt;/code&gt; on the wire and becomes their inbound &lt;code&gt;requestId&lt;/code&gt; -- the "outbound" qualifier is only meaningful here, on the caller side.&lt;/p&gt;

&lt;p&gt;Chef clips ticket-4 to the rail. "Fire table 4, two salmon!" LineCook is buried under a stack of tickets, three pans going. No response. Chef shouts again, louder. Still nothing. Third call, LineCook finally yells back "Heard!" Each shout lands in the log under the same ticket number (correlation ID) with a fresh shout ID (request ID).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr862mqi9kz3roqnrr71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr862mqi9kz3roqnrr71.png" alt="Shout retry" width="546" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Shout IDs above are numbered from 1 for clarity. In the full trace at the end of the article, they follow the global sequence across all five stations.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Orders to the suppliers (your external outbound calls)
&lt;/h2&gt;

&lt;p&gt;Sometimes Chef needs ingredients the kitchen doesn't make. The bread comes from the bakery, the fish from the fishmonger, the produce from the market. When Chef calls the fishmonger to order 5 lbs of salmon by 10am, the kitchen's internal table ticket is irrelevant to the fishmonger -- and forwarding it would tell them more about your operation than they need to know.&lt;/p&gt;

&lt;p&gt;The same applies to outbound HTTP calls to third-party APIs (payment processors, mapping services, vendor integrations). Forwarding your internal &lt;code&gt;x-correlation-id&lt;/code&gt; and &lt;code&gt;x-request-id&lt;/code&gt; headers to an external service leaks information about your request flow into a system that doesn't need it, and may store it in their logs indefinitely. Scope the outbound interceptor to your internal HTTP clients only, or add a URL allowlist that excludes external hosts.&lt;/p&gt;

&lt;p&gt;If the third-party returns its own correlation or request ID on the response, record it on your side. It's the thread that ties your logs to theirs when you need to debug a cross-system issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeout orders (async messaging)
&lt;/h2&gt;

&lt;p&gt;The pattern above is HTTP-shaped, but the boundary is the only thing that matters. A Kafka consumer, SQS handler, or Kinesis processor pulls the correlation ID out of message headers or record metadata, calls &lt;code&gt;ExecutionContext.run(...)&lt;/code&gt;, and from there the rest of the code is identical. The transport changes; the approach doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  One trace, one picture
&lt;/h2&gt;

&lt;p&gt;Five stations, one table ticket. The correlation ID is the value that doesn't change. The request ID is the value that does -- every shout from Chef to a station gets its own, every retry gets a fresh one, every downstream call too. Read the trace by following the correlation ID down the page; read the retries by spotting the request IDs that bunch up under the same correlation ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fja3oq4ojxd57287n0ylk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fja3oq4ojxd57287n0ylk.png" alt="Full Sequence" width="784" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When this stops being enough
&lt;/h2&gt;

&lt;p&gt;This pattern -- correlation ID at the boundary, request ID per hop, both on every log line -- holds up until your system grows in complexity. When the debugging question becomes &lt;em&gt;where in this 14-service flow did 80% of the latency get spent?&lt;/em&gt;, you've outgrown correlation IDs and you want OpenTelemetry traces. Last9's piece on correlation ID vs trace ID (in References below) is the right next read for that step -- it goes deeper into trace IDs than this article wanted to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same name, different recipe
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;x-correlation-id&lt;/code&gt; is used differently across the industry. This article treats it as a trace ID -- fresh per inbound boundary entry, propagated across downstream calls. &lt;a href="https://developer.adobe.com/vipmp/docs/references/idempotency" rel="noopener noreferrer"&gt;Adobe's API&lt;/a&gt; uses it as an idempotency key instead -- a stable identifier the client reuses across retries of the same business intent so the server can return the cached response. The two uses are incompatible. If you're integrating with a service that enforces idempotency on &lt;code&gt;x-correlation-id&lt;/code&gt;, pick a different header (or namespace your own) for the trace ID described here. &lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;Stripe's &lt;code&gt;Idempotency-Key&lt;/code&gt;&lt;/a&gt; is the cleaner pattern -- a dedicated header for idempotency, no collision with tracing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the paring knife
&lt;/h2&gt;

&lt;p&gt;Two value objects, one async-local context, an &lt;code&gt;ExecutionContext&lt;/code&gt;-aware logger, a couple of middlewares, one interceptor. Reach for the cleaver when the system asks for it, not before. This is what worked in real systems -- search &lt;code&gt;correlation_id&lt;/code&gt; in my &lt;a href="https://ouimet.info/#career-changelog" rel="noopener noreferrer"&gt;career changelog&lt;/a&gt; to see where.&lt;/p&gt;

&lt;p&gt;Open the logs. Filter by correlation ID and you have the journey. Filter by request ID and you have the one retry, alone, no noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hilton.org.uk/blog/microservices-correlation-id" rel="noopener noreferrer"&gt;Microservices Correlation ID (Hilton)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.adobe.com/vipmp/docs/references/idempotency" rel="noopener noreferrer"&gt;Idempotency (Adobe)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://last9.io/blog/correlation-id-vs-trace-id/" rel="noopener noreferrer"&gt;Correlation ID vs Trace ID (Last9)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rapid7.com/blog/post/2016/12/23/the-value-of-correlation-ids/" rel="noopener noreferrer"&gt;The Value of Correlation IDs (Rapid7)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/stream-zero/correlation-ids-in-enterprise-architecture-d5851df23da0" rel="noopener noreferrer"&gt;Correlation IDs in Enterprise Architecture (StreamZero)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/hackernoon/how-i-added-awesome-multi-threaded-features-to-express-js-753452a1c10e" rel="noopener noreferrer"&gt;How I Added Multi-Threaded Features to Express.js (Hackernoon)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://koder.ai/blog/correlation-ids-end-to-end-tracing" rel="noopener noreferrer"&gt;Correlation IDs: End-to-End Tracing (Koder)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techblog.realtor.com/microservice-error-tracing-using-correlation-ids/" rel="noopener noreferrer"&gt;Microservice Error Tracing Using Correlation IDs (Realtor.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.use.id/docs/logging-request-ids-and-correlation-ids" rel="noopener noreferrer"&gt;Logging Request IDs and Correlation IDs (use.id)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>observability</category>
      <category>distributedsystems</category>
      <category>backend</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Fix: "There's an issue with the selected model (deepseek-v4-pro)" in Claude CLI</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Fri, 05 Jun 2026 03:59:10 +0000</pubDate>
      <link>https://dev.to/couimet/fix-theres-an-issue-with-the-selected-model-deepseek-v4-pro-in-claude-cli-44jg</link>
      <guid>https://dev.to/couimet/fix-theres-an-issue-with-the-selected-model-deepseek-v4-pro-in-claude-cli-44jg</guid>
      <description>&lt;p&gt;If you landed here from a search engine, here's the error you saw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;There's an issue with the selected model (deepseek-v4-pro). It may not exist or you may not have access to it. Run /model to pick a different model.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You had the eight env vars from my &lt;a href="https://dev.to/couimet/eight-env-vars-and-claude-code-stops-eating-my-paycheck-2659"&gt;previous article&lt;/a&gt; set. You opened a fresh terminal, typed &lt;code&gt;claude&lt;/code&gt;, and got that. Same.&lt;/p&gt;

&lt;p&gt;A quick glossary for this article: &lt;code&gt;claude-code&lt;/code&gt; is the brew package. &lt;code&gt;claude&lt;/code&gt; is the command you type in your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  I thought Anthropic was on to me
&lt;/h2&gt;

&lt;p&gt;I'll admit where my brain went first: Anthropic had patched &lt;code&gt;claude-code&lt;/code&gt; to block non-Anthropic API keys. DeepSeek built a fully backwards-compatible API: same endpoints, same request shape, same response format. Anthropic ships a new &lt;code&gt;claude-code&lt;/code&gt; version, I run &lt;code&gt;brew upgrade&lt;/code&gt; without thinking, and suddenly my DeepSeek setup breaks. The timing was too clean.&lt;/p&gt;

&lt;p&gt;And here's the really self-absorbed part of this theory: my article explaining the DeepSeek setup had been live on dev.to for a couple weeks. It was getting reads. What if someone at Anthropic saw it and that was their "alright, that's enough" moment? A human being in San Francisco read my post, opened a Jira ticket, and a week later the validation shipped.&lt;/p&gt;

&lt;p&gt;Classic Coyote: draw the plan, check the ground later. Anthropic, a company with thousands of employees building frontier AI, was not reacting to my dev.to post. They probably have bigger things to worry about than one person's $10 DeepSeek bill. But the theory &lt;em&gt;felt&lt;/em&gt; good, and the error message didn't give me much else to work with.&lt;/p&gt;

&lt;p&gt;I also googled the exact error string. Zero English results. A few forum posts, all written in 中文. That vacuum didn't help. When nobody in English is talking about an error, your brain writes its own screenplay. Mine went full &lt;a href="https://en.wikipedia.org/wiki/Wile_E._Coyote_and_the_Road_Runner" rel="noopener noreferrer"&gt;Wile E. Coyote&lt;/a&gt;, sketching elaborate traps that weren't there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually happening
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;claude&lt;/code&gt; wasn't contacting DeepSeek at all. It was talking to Anthropic.&lt;/p&gt;

&lt;p&gt;The reason was a missing newline in my setup script. After publishing the original article, I'd moved the eight &lt;code&gt;export&lt;/code&gt; lines into &lt;code&gt;~/point-to-deepseek.sh&lt;/code&gt; so I could &lt;code&gt;source&lt;/code&gt; the file instead of pasting the block into my terminal. API keys are like underwear: you don't share them, and pasting one into a terminal left it sitting in my scrollback for anyone to see.&lt;/p&gt;

&lt;p&gt;In that file, two lines had merged into one. The line starting with &lt;code&gt;export ANTHROPIC_BASE_URL=...&lt;/code&gt; had the &lt;code&gt;export ANTHROPIC_AUTH_TOKEN=...&lt;/code&gt; line glued to its end, with no newline between them. The shell saw one corrupt line. &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; never got exported. &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; got set to nonsense.&lt;/p&gt;

&lt;p&gt;Coyote looked down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# what the shell saw (broken)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.deepseek.com/anthropic &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-xxx

&lt;span class="c"&gt;# what it should look like (fixed)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.deepseek.com/anthropic
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A related edge case: resuming a DeepSeek session in an Anthropic context
&lt;/h2&gt;

&lt;p&gt;I said my panic was a false alarm, but one thing has actually changed since I wrote the original setup guide. When I first published it, I was routinely starting sessions on one provider and resuming them on the other — mostly DeepSeek sessions that I'd pick back up on Anthropic after my 5-hour quota reset. That stopped working about a week or two ago.&lt;/p&gt;

&lt;p&gt;If you start a &lt;code&gt;claude&lt;/code&gt; session pointed at DeepSeek, &lt;code&gt;/exit&lt;/code&gt;, then try to &lt;code&gt;/resume&lt;/code&gt; it from a &lt;code&gt;claude&lt;/code&gt; instance pointed at Anthropic, you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Error: 400 messages.1.content.0: Invalid `signature` in `thinking` block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reverse still works. I resume Anthropic sessions on DeepSeek every day with no issues. So it's not a two-way wall — it's a one-way door, and it only catches you going in one direction.&lt;/p&gt;

&lt;p&gt;Whether it's a deliberate countermeasure or a format mismatch between two implementations of the same API is unconfirmed. But unlike my imaginary &lt;code&gt;claude-code&lt;/code&gt; patch, this one is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm really writing this
&lt;/h2&gt;

&lt;p&gt;I said zero English results. That vacuum is the real reason this article exists. Somebody is going to hit this error, google it, and land here. That's the bet.&lt;/p&gt;

&lt;p&gt;And now I'm publishing a second article about a missing newline, SEO-optimized around an error message, hoping Google indexes it before the next person loses time to this.&lt;/p&gt;

&lt;p&gt;I'd spent half an hour convinced I was the protagonist in a David-and-Goliath story about API compatibility. Anthropic had noticed my workaround. They'd shipped a countermeasure. I was drafting the exposé in my head. Classic Coyote.&lt;/p&gt;

&lt;p&gt;The eight env vars still work. I'm still using them.&lt;/p&gt;




&lt;p&gt;If you got here from the error message, I hope this saved you the 30 minutes I lost. Never saw the original setup? &lt;a href="https://dev.to/couimet/eight-env-vars-and-claude-code-stops-eating-my-paycheck-2659"&gt;It's here.&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;My friend Joel, after I shared &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7463240605780074496/" rel="noopener noreferrer"&gt;a recent article on LinkedIn&lt;/a&gt;: "You can't teach old dogs new tricks, but you can force them to become LinkedIn influencers!" I am now publishing a second one about a missing newline. Yeah, I'm looking at you, dear Joel. 😉&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>deepseek</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>I sold you on /scratchpad. Then I migrated to /note.</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Thu, 21 May 2026 13:21:14 +0000</pubDate>
      <link>https://dev.to/couimet/i-sold-you-on-scratchpad-then-i-migrated-to-note-4n1o</link>
      <guid>https://dev.to/couimet/i-sold-you-on-scratchpad-then-i-migrated-to-note-4n1o</guid>
      <description>&lt;p&gt;This is the third post in a series that started with &lt;a href="https://dev.to/couimet/from-vide-coding-to-supercharged-vibe-guiding-6nm"&gt;vibe guiding&lt;/a&gt; and continued with an &lt;a href="https://dev.to/couimet/i-thought-i-was-dry-ing-i-may-have-been-double-paying-5dli"&gt;efficiency audit&lt;/a&gt;. Two months ago I laid out the four-skill loop: &lt;code&gt;/start-issue&lt;/code&gt; writes a &lt;code&gt;/scratchpad&lt;/code&gt;, &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt; executes one step at a time, status flags transition from &lt;code&gt;pending&lt;/code&gt; to &lt;code&gt;done&lt;/code&gt;, and &lt;code&gt;/finish-issue&lt;/code&gt; reads what shipped and writes the PR title + description.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I changed my mind
&lt;/h2&gt;

&lt;p&gt;The driver was token consumption. My employer gave each team dashboards of our AI tool usage and I was always the top consumer on mine — fairly high on the department chart too. That's when I started to suspect it was not what I was building but how I was working with the tools.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/scratchpad&lt;/code&gt;'s JSON step block and &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt; chain meant every task paid the same ceremony tax: status flags to transition, JSON to validate, a fenced block to parse. Useful scaffolding for multi-hour work. Overhead I was paying on every session. If my process was the reason I topped the dashboard, revisiting it was the cheapest experiment I could run.&lt;/p&gt;

&lt;p&gt;Once I started looking, the redundancy wasn't only scratchpad-vs-note. The templates the composite skills were generating — what &lt;code&gt;/start-issue&lt;/code&gt;, &lt;code&gt;/start-side-quest&lt;/code&gt;, &lt;code&gt;/tackle-pr-comment&lt;/code&gt;, and &lt;code&gt;/finish-issue&lt;/code&gt; wrote into their output documents — carried weight they didn't need. A few examples from the audit that led to &lt;a href="https://github.com/couimet/my-claude-skills/pull/130" rel="noopener noreferrer"&gt;PR 130&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;## Files to Modify&lt;/code&gt; re-grouped information the Plan steps already named, just organized by file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;## Documentation &amp;amp; Discoverability&lt;/code&gt; was a pre-populated checklist that &lt;code&gt;/finish-issue&lt;/code&gt; already re-derived systematically at wrap-up time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;## Acceptance Criteria&lt;/code&gt; had Claude copy criteria verbatim from the issue body it already had in context.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;## Why Split This Out&lt;/code&gt; was three hardcoded bullets that matched every side-quest ever created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these was small on its own. Cumulatively they were a per-session surtax, paid every time a skill fired. Defaulting to &lt;code&gt;/note&lt;/code&gt; instead of &lt;code&gt;/scratchpad&lt;/code&gt; was the biggest single cut, but the pattern of the change was wider: stop having the LLM write the same information twice, in different shapes, into different documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude was changing too
&lt;/h2&gt;

&lt;p&gt;The other thing that shifted in those months was Claude itself. When I first wrote about this, the hard stops at every step made sense for the Claude that was around then. By the time &lt;a href="https://github.com/couimet/my-claude-skills/pull/130" rel="noopener noreferrer"&gt;PR 130&lt;/a&gt; landed, Claude had gotten noticeably better at multi-step self-organization across an issue. I could lower my guard a bit. The &lt;em&gt;control-freak posture&lt;/em&gt; I had started with was no longer needed. If I wanted an explicit checkpoint to manually review and commit, I could simply add a step in the generated &lt;code&gt;/note&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Parallelism shifted too. Claude gained the ability to fan out workers and run agents in parallel within a single session, which meant on issues with independent pieces Claude could organize its own throughput faster than I could shepherd it through the &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt; chain. The gates that had once added safety started adding latency. By gating with &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt;, I was slowing Claude down by asking for more control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;/note&lt;/code&gt; looks like
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;/scratchpad&lt;/code&gt;'s &lt;code&gt;## Implementation Plan&lt;/code&gt; section is a fenced JSON block with status fields, dependency arrays, and task lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"finish_issue_on_complete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"S001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Swap the token library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"done_when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Old lib removed from package.json, new one imported and passing existing tests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"depends_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"package.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/auth/token.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"npm install new-token-lib, npm remove old-token-lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Update src/auth/token.ts imports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Run the token test suite"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"S002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Update callers to match new token API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"depends_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"S001"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/middleware/auth.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/routes/login.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Update verifyToken() call sites to new API shape"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Run full test suite"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;/note&lt;/code&gt;'s &lt;code&gt;## Plan&lt;/code&gt; section is the same information without the scaffolding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Swap the token library — npm install new-token-lib, update imports, run the token test suite
2. Update callers to match the new token API — verifyToken() call sites, full test suite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No fenced JSON block. No &lt;code&gt;status: pending&lt;/code&gt;. The plan says what to do and in what order; the LLM organizes its own execution in-session. The hard stops from the original workflow are still there: I read the plan before saying "go ahead", I review the diff, I commit when I'm satisfied. The commit lands at the end of the note, not after every step. If I want an earlier checkpoint, I add an explicit gate in the plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foac83codonnv3gqkcrfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foac83codonnv3gqkcrfj.png" alt="Note-vs-scratchpad workflow" width="586" height="948"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/scratchpad&lt;/code&gt; is still there. You opt in two ways: pass &lt;code&gt;--scratchpad&lt;/code&gt; to the invoking skill, or use one of the natural-language triggers the skill watches for ("use a scratchpad", "with step tracking", "formal plan", "track steps"). I still reach for it when I'm working through a larger GitHub issue and want a commit after each step instead of one at the end. This is especially helpful when I'm juggling two or three worktrees on the same project — iterative commits and step tracking keep me oriented when my attention is split across parallel branches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am taking away
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/note&lt;/code&gt; flow still stops for plan review before anything happens, then gives the LLM more autonomy to fan out and move faster. The &lt;code&gt;/scratchpad&lt;/code&gt; flow is still powerful — but you don't always need a bazooka to kill a fly. Most days the lighter tool is enough, ending on a high &lt;em&gt;note&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you already use these skills
&lt;/h2&gt;

&lt;p&gt;Pull the latest from the repo and run &lt;code&gt;./setup.sh&lt;/code&gt; to symlink the updated set. &lt;code&gt;/start-issue&lt;/code&gt; will produce a &lt;code&gt;/note&lt;/code&gt; on your next invocation. Nothing else changes — the rest of the loop carries on. If you want the old behavior, type &lt;code&gt;--scratchpad&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written using the same skills it describes, starting from &lt;a href="https://github.com/couimet/my-claude-skills/issues/139" rel="noopener noreferrer"&gt;issue #139&lt;/a&gt;. The plan was a note this time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Eight env vars and Claude Code stops eating my paycheck</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Tue, 12 May 2026 13:19:32 +0000</pubDate>
      <link>https://dev.to/couimet/eight-env-vars-and-claude-code-stops-eating-my-paycheck-2659</link>
      <guid>https://dev.to/couimet/eight-env-vars-and-claude-code-stops-eating-my-paycheck-2659</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-06-04):&lt;/strong&gt; Running into model errors with this setup? &lt;a href="https://dev.to/couimet/fix-theres-an-issue-with-the-selected-model-deepseek-v4-pro-in-claude-cli-44jg"&gt;Read the investigation here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DeepSeek's quick-start page has been live for months. I'm late to it.&lt;/p&gt;

&lt;p&gt;I love what Anthropic ships. Opus 4.7 is the best coding model I've used. I also can't run it all day on a family budget, and Pro's 5-hour quota wall stopped my flow more times than I want to count. So a few days ago I exported eight env vars, pointed Claude Code at DeepSeek, and went back to building &lt;a href="https://ouimet.info/projects/rangelink-extension.html" rel="noopener noreferrer"&gt;rangeLink&lt;/a&gt;. Since then I've only spent $4 and haven't seen a rate limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight env vars
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.deepseek.com/anthropic
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-deepseek-key&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deepseek-v4-pro
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_OPUS_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deepseek-v4-pro
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deepseek-v4-pro
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_HAIKU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deepseek-v4-flash
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_SUBAGENT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deepseek-v4-flash
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_EFFORT_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that block in your terminal, then invoke &lt;code&gt;claude-code&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's going on in there
&lt;/h2&gt;

&lt;p&gt;The subagent model points to &lt;code&gt;deepseek-v4-flash&lt;/code&gt; instead of &lt;code&gt;v4-pro&lt;/code&gt;. Claude Code spawns subagents for grep-and-summarize work, and there's no reason to pay pro rates for "find the file that has this string." Flash is good enough for that, and it returns faster.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CLAUDE_CODE_EFFORT_LEVEL=max&lt;/code&gt; keeps the quality slider cranked on the cheap path. The flag exists; you may as well use it.&lt;/p&gt;

&lt;p&gt;Anthropic's three tiers (Opus, Sonnet, Haiku) get collapsed onto DeepSeek's two. Opus and Sonnet both map to &lt;code&gt;v4-pro&lt;/code&gt; because that's the strongest model DeepSeek serves. Haiku goes to &lt;code&gt;v4-flash&lt;/code&gt; because nothing about Haiku-class work needs more than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm still using this a week later
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; I bought $10 of &lt;code&gt;deepseek-v4-pro&lt;/code&gt; credits last week. After five days of 3-6 hours a day on rangeLink, $6 is still on the meter. I stopped checking the balance after day three because it wasn't going anywhere fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No 5-hour quota wall.&lt;/strong&gt; Anthropic Pro's quota window resets on a 5-hour cycle, and hitting it mid-task is the worst kind of interruption. You've built up context, you're in the middle of something, and now you're either waiting or paying for top-up. Since I switched: zero waits. Not "fewer." Zero.&lt;/p&gt;

&lt;p&gt;The second one matters more than I expected. Going in, I was solving for cost. I figured rate limits were a nice-to-have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch I hit
&lt;/h2&gt;

&lt;p&gt;Pasted images in the terminal don't make it through to DeepSeek's API. If your workflow leans on screenshots — "look at this dev-tools error" or "match this design" — this swap will hurt.&lt;/p&gt;

&lt;p&gt;Community work is happening here. I'm not going to recommend anything I haven't fully tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same trick, different provider
&lt;/h2&gt;

&lt;p&gt;A friend was already running DeepSeek with &lt;a href="https://docs.openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; and happy with it, so I copied his pick when I made the switch. Two worth a look as of May 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Moonshot Kimi K2.5&lt;/strong&gt; — &lt;code&gt;ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic&lt;/code&gt;, model &lt;code&gt;kimi-k2.5&lt;/code&gt;. &lt;a href="https://platform.kimi.ai/docs/guide/agent-support" rel="noopener noreferrer"&gt;Moonshot's Claude Code setup docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Z.ai GLM-5.1&lt;/strong&gt; — &lt;code&gt;ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic&lt;/code&gt;, model &lt;code&gt;glm-5.1&lt;/code&gt;. &lt;a href="https://docs.z.ai/devpack/tool/claude" rel="noopener noreferrer"&gt;Z.AI Claude Code docs&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've run Kimi or GLM with Claude Code, I'd be curious how it went.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on privacy
&lt;/h2&gt;

&lt;p&gt;Your prompts go to DeepSeek's servers. Fine for my public-repo work. Pick a provider whose data policy matches your repos' sensitivity.&lt;/p&gt;




&lt;p&gt;Five days, $4 spent, zero rate-limit walls.&lt;/p&gt;

&lt;p&gt;If your employer foots the Anthropic bill, or your wallet can swing a Max plan, by all means — Opus 4.7 is still the best at the top end. If that's not you, eight env vars.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Co-Authored-By: Claude &amp;lt;noreply@anthropic.com&amp;gt; (running on deepseek-v4-pro)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>claude</category>
      <category>deepseek</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I thought I was DRY-ing. I may have been double-paying.</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Tue, 21 Apr 2026 12:30:29 +0000</pubDate>
      <link>https://dev.to/couimet/i-thought-i-was-dry-ing-i-may-have-been-double-paying-5dli</link>
      <guid>https://dev.to/couimet/i-thought-i-was-dry-ing-i-may-have-been-double-paying-5dli</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-05-21):&lt;/strong&gt; I've kept iterating since this post. See &lt;a href="https://dev.to/couimet/i-sold-you-on-scratchpad-then-i-migrated-to-note-4n1o"&gt;the move to /note&lt;/a&gt; for the May 2026 shift.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Something was off
&lt;/h2&gt;

&lt;p&gt;A few weeks after publishing &lt;a href="https://dev.to/couimet/from-vide-coding-to-supercharged-vibe-guiding-6nm"&gt;From Vide Coding to Supercharged Vibe Guiding&lt;/a&gt;, I landed &lt;a href="https://github.com/couimet/my-claude-skills/pull/121" rel="noopener noreferrer"&gt;pull/121&lt;/a&gt; — a follow-up refactor that made &lt;code&gt;/scratchpad&lt;/code&gt;, &lt;code&gt;/question&lt;/code&gt;, and &lt;code&gt;/commit-msg&lt;/code&gt; "self-contained" by inlining a foundation skill's logic into each of them. I was expecting lighter sessions afterwards. I couldn't tell if I got them. The next refactor on my list was "inline more of these foundations" and I caught myself about to make the same bet twice — without ever having checked whether the first one paid off.&lt;/p&gt;

&lt;p&gt;My theory at that point, the one driving both refactors, was that the foundation skills were what cost too much. &lt;code&gt;/code-ref&lt;/code&gt; for code-link format, &lt;code&gt;/github-ref&lt;/code&gt; for issue-URL format, &lt;code&gt;/issue-context&lt;/code&gt; for figuring out which directory a scratchpad should go in — all auto-consulted by Claude constantly. The working assumption was that the cross-references themselves were the tax. Inline the rules, kill the foundations, buy the tokens back.&lt;/p&gt;

&lt;p&gt;My hypothesis may have had the cost in the wrong place. At least, an independent audit thought it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I let another Claude run the audit
&lt;/h2&gt;

&lt;p&gt;Doing the audit myself would have confirmed my bias. So I opened a fresh Claude Code session, told it explicitly not to run my own &lt;code&gt;/audit-efficiency&lt;/code&gt; skill, not to read my README or CHANGELOG, and asked six specific questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where is token budget being spent unnecessarily?&lt;/li&gt;
&lt;li&gt;How often is each overhead paid (once per session, once per invocation, once per skill type)?&lt;/li&gt;
&lt;li&gt;Rank the findings with your own labels. Don't use the HIGH/MEDIUM/LOW my existing audit skill uses.&lt;/li&gt;
&lt;li&gt;For each top finding, propose at least two fixes including "do nothing."&lt;/li&gt;
&lt;li&gt;Separately, flag anti-patterns about reliability and maintainability, not just efficiency.&lt;/li&gt;
&lt;li&gt;End with: "What question would you have asked me before starting this audit if you could?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;The full prompt, verbatim, with a short note on its design rationale, lives in the repo at &lt;a href="https://github.com/couimet/my-claude-skills/blob/main/docs/run-an-audit.md" rel="noopener noreferrer"&gt;docs/run-an-audit.md&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Claude dropped a timestamped report in &lt;code&gt;/tmp/&lt;/code&gt;. I read it cold. The executive summary opened with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The largest recurring cost in this collection is a self-inflicted duplication of branch-parsing + filename logic inlined into &lt;code&gt;scratchpad&lt;/code&gt;, &lt;code&gt;question&lt;/code&gt;, and &lt;code&gt;commit-msg&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The day before, I'd refactored those three skills to make them "self-contained" — taken about 35 lines of branch-parsing Markdown out of a shared foundation and pasted the same block into each caller. The idea was to stop paying for the foundation's auto-consultation. What actually happened: the foundation kept auto-consulting anyway (its description line advertised itself for exactly that workflow), the same logic was now sitting in three places, and the copies had already started drifting. The foundation's examples list didn't match the inlined copies and I hadn't noticed. So I had two copies of drifting logic and, if the audit's cost model is right, was paying for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  My hypothesis was backwards
&lt;/h2&gt;

&lt;p&gt;I'd gone in assuming DRY-via-cross-references was the tax. The audit's sharper version was more like: &lt;em&gt;half-finished migrations can cost tokens, and deterministic logic in Markdown tends to cost tokens whether it's DRY or not.&lt;/em&gt; That's a working theory, not a measurement — but it's a theory I was already primed to believe once the drift was in front of me.&lt;/p&gt;

&lt;p&gt;The audit pointed at two skills it thought were well-shaped — &lt;code&gt;/auto-number&lt;/code&gt; and &lt;code&gt;/ensure-gitignore&lt;/code&gt;. Both are foundation skills whose SKILL.md just documents a shell-script contract. The script does the work. Claude calls it and reads one line of stdout. Zero reasoning burned per invocation.&lt;/p&gt;

&lt;p&gt;That was the pattern I should have been trying to extend. With the audit in hand, &lt;a href="https://github.com/couimet/my-claude-skills/pull/121" rel="noopener noreferrer"&gt;pull/121&lt;/a&gt; looks like the wrong direction — it added more Markdown where, in retrospect, a shell script fits better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit found
&lt;/h2&gt;

&lt;p&gt;Findings in my paraphrase of the audit, ranked by how often it thought the cost was paid:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F1, per-invocation bleed.&lt;/strong&gt; The branch-parsing + filename block inlined into &lt;code&gt;/scratchpad&lt;/code&gt;, &lt;code&gt;/question&lt;/code&gt;, and &lt;code&gt;/commit-msg&lt;/code&gt;. Paid every time any of them fires, plus transitively when &lt;code&gt;/start-issue&lt;/code&gt;, &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt;, &lt;code&gt;/finish-issue&lt;/code&gt;, and &lt;code&gt;/tackle-pr-comment&lt;/code&gt; invoke them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F2, per-session surtax.&lt;/strong&gt; A three-line "Output Format" epilogue (hard-wrap rule, code-reference rule, GitHub-URL rule) copy-pasted into 11 skills. Two existing foundations (&lt;code&gt;/code-ref&lt;/code&gt;, &lt;code&gt;/github-ref&lt;/code&gt;) already covered the same rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F3, structural debt.&lt;/strong&gt; &lt;code&gt;/issue-context&lt;/code&gt; was in a halfway state. Its content had drifted from the inlined copies. A direction had to be picked and finished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F4, per-session surtax.&lt;/strong&gt; The two longest skill descriptions (&lt;code&gt;/scratchpad&lt;/code&gt; at 316 chars, &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt; at 275) loaded into the catalog on every session, even when neither was invoked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F5, structural debt verging on bleed.&lt;/strong&gt; The step-JSON schema redrawn in five places instead of once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot
&lt;/h2&gt;

&lt;p&gt;Before any edits, I worked through six scoping questions in a &lt;code&gt;/question&lt;/code&gt; file and had the Claude session read them back. One question carried most of the weight: pick &lt;em&gt;delete-and-script&lt;/em&gt;, &lt;em&gt;delete-and-inline-more&lt;/em&gt;, &lt;em&gt;restore-the-foundation&lt;/em&gt;, or &lt;em&gt;status quo&lt;/em&gt; for &lt;code&gt;/issue-context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I picked the script route. I'd already done it twice. &lt;code&gt;/auto-number&lt;/code&gt; collapses "scan this directory, find the highest number, add one, zero-pad it" into one Bash call. &lt;code&gt;/ensure-gitignore&lt;/code&gt; collapses "read the file, check for the sentinel, append if missing" the same way. "Read the branch, extract the issue ID, decide where the file goes, slugify the description, and auto-number it" is the same shape of problem.&lt;/p&gt;

&lt;p&gt;I called it &lt;code&gt;target-path.sh&lt;/code&gt;. One call, one line of stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;skills/issue-context/target-path.sh &lt;span class="nt"&gt;--type&lt;/span&gt; scratchpads &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"audit follow-up"&lt;/span&gt;
&lt;span class="c"&gt;# → .claude-work/issues/120/scratchpads/0003-audit-follow-up.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of &lt;code&gt;/scratchpad&lt;/code&gt;, &lt;code&gt;/question&lt;/code&gt;, and &lt;code&gt;/commit-msg&lt;/code&gt; dropped from a roughly 45-line Step 1 block to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Run these two commands as parallel tool calls. They are independent.
&lt;span class="p"&gt;
-&lt;/span&gt; skills/issue-context/target-path.sh --type &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt; --description "$ARGUMENTS"
&lt;span class="p"&gt;-&lt;/span&gt; skills/ensure-gitignore/ensure-gitignore.sh

Use the stdout of the first command as the full file path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/issue-context&lt;/code&gt; foundation went from 118 lines of branch-parsing prose to 30 lines that just document the script's contract.&lt;/p&gt;

&lt;p&gt;For F2, I folded the hard-wrap rule plus the two reference-format rules into a single new &lt;code&gt;/prose-style&lt;/code&gt; foundation. Then I deleted the epilogues from all 11 callers and replaced each with a one-line pointer: &lt;code&gt;Formatting: see /prose-style&lt;/code&gt;. The standalone &lt;code&gt;/code-ref&lt;/code&gt; and &lt;code&gt;/github-ref&lt;/code&gt; foundations got folded into &lt;code&gt;/prose-style&lt;/code&gt; too, then deleted. Side effect: a dangling symlink at &lt;code&gt;~/.claude/skills/prose-style&lt;/code&gt; that had been pointing at a non-existent directory for months finally had something to point at. I hadn't noticed it was broken.&lt;/p&gt;

&lt;p&gt;F4 (descriptions) and F5 (JSON schema redrawn) were one-shot edits. Shrink two descriptions. Replace three inline JSON blocks with references to the authoritative schema in &lt;code&gt;/scratchpad&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this might save in tokens
&lt;/h2&gt;

&lt;p&gt;Counting SKILL.md content only, the before state was roughly 25,600 tokens and the after state is roughly 21,500. About 4,100 tokens lighter, call it 16%. Treat every token count here as a ~4-chars-per-token ballpark, not a microbenchmark.&lt;/p&gt;

&lt;p&gt;The static diff is the part I can actually count. Everything beyond that is arithmetic on the audit's cost model, not a measurement — token consumption is hard to predict when you're not deep in how these systems load context. So take what follows as "if the audit is roughly right, this is what the math would look like."&lt;/p&gt;

&lt;p&gt;On a typical full-time coding day I run this loop 25+ times: plan via &lt;code&gt;/scratchpad&lt;/code&gt;, surface a design question via &lt;code&gt;/question&lt;/code&gt; when something needs one, tackle a block, draft a &lt;code&gt;/commit-msg&lt;/code&gt;. That's only about three cycles an hour. When I'm iterating on small pieces, I easily hit 10 cycles an hour. By lunchtime I've already blown past what used to feel like a full day's worth of context.&lt;/p&gt;

&lt;p&gt;If the audit's model holds, each cycle was spending around 140 tokens of branch-parsing Markdown re-reasoned per &lt;code&gt;/scratchpad&lt;/code&gt; call, the same pattern on &lt;code&gt;/question&lt;/code&gt; and &lt;code&gt;/commit-msg&lt;/code&gt;, plus the &lt;code&gt;/issue-context&lt;/code&gt; foundation body pulled in by description match whenever any of them fired. Call it 500 to 1,000 tokens per cycle in duplicated logic.&lt;/p&gt;

&lt;p&gt;Multiply by 25 cycles and the refactor might buy back somewhere in the tens of thousands of tokens a day. Double that on a heavy iteration day. The 4,100 tokens I shaved off the static diff is the boring headline; the number that might actually change my workday is closer to 25,000 to 50,000 tokens not being burned on the same deterministic logic over and over. Would I have noticed that in practice? Honestly, I don't know. The real move — the one I should have started with — is to measure often and keep optimizing for whatever actually shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking away
&lt;/h2&gt;

&lt;p&gt;"DRY is good" and "DRY costs tokens" feel like slogans that don't quite capture what I ran into. What I'm starting to think matters more is whether the logic you're trying to deduplicate is deterministic. When it is, my bet is Markdown's the wrong container — a script that returns one line of stdout fits the shape better, and lets Claude spend its reasoning on the parts that need it. The three scripts carrying the load here (&lt;code&gt;auto-number.sh&lt;/code&gt;, &lt;code&gt;ensure-gitignore.sh&lt;/code&gt;, and now &lt;code&gt;target-path.sh&lt;/code&gt;) all fit that shape.&lt;/p&gt;

&lt;p&gt;The halfway migration is the one the audit saved me from. My previous refactor inlined three skills and left the foundation in place, and I'd have kept maintaining both without realizing the shape was broken. Committing to either direction would probably have been cheaper than the in-between state I shipped.&lt;/p&gt;

&lt;p&gt;One more thing about the audit itself. If I'd run it myself I'd have confirmed my hypothesis and doubled down on inlining — the same move that caused the problem in the first place. Pointing a fresh Claude session explicitly at what &lt;em&gt;not&lt;/em&gt; to look at was the cheapest way I had to step outside my own framing. Obvious in hindsight. Wasn't at the time.&lt;/p&gt;

&lt;p&gt;Which leads to the last decision in this refactor: I deleted my own &lt;code&gt;/audit-efficiency&lt;/code&gt; skill. Once the cold audit clearly produced a stronger report, keeping the biased version around made it likely I'd reach for it next time. The prompt from this post replaces it at &lt;a href="https://github.com/couimet/my-claude-skills/blob/main/docs/run-an-audit.md" rel="noopener noreferrer"&gt;docs/run-an-audit.md&lt;/a&gt; — a file you paste from, not a skill you invoke. That's the whole operational artifact now.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try this
&lt;/h2&gt;

&lt;p&gt;The prompt lives in the repo at &lt;a href="https://github.com/couimet/my-claude-skills/blob/main/docs/run-an-audit.md" rel="noopener noreferrer"&gt;docs/run-an-audit.md&lt;/a&gt;. Copy it and paste it into a fresh Claude Code session.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written alongside &lt;a href="https://github.com/couimet/my-claude-skills/pull/122" rel="noopener noreferrer"&gt;the refactor PR itself&lt;/a&gt; using the same skills it describes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From Vide Coding to Supercharged Vibe Guiding</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Mon, 16 Mar 2026 13:52:14 +0000</pubDate>
      <link>https://dev.to/couimet/from-vide-coding-to-supercharged-vibe-guiding-6nm</link>
      <guid>https://dev.to/couimet/from-vide-coding-to-supercharged-vibe-guiding-6nm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-05-21):&lt;/strong&gt; I've kept iterating on this workflow since publishing. See &lt;a href="https://dev.to/couimet/i-thought-i-was-dry-ing-i-may-have-been-double-paying-5dli"&gt;the audit pivot&lt;/a&gt; for what changed in April 2026, and &lt;a href="https://dev.to/couimet/i-sold-you-on-scratchpad-then-i-migrated-to-note-4n1o"&gt;the move to /note&lt;/a&gt; for the May 2026 shift.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  It's Not a Typo
&lt;/h2&gt;

&lt;p&gt;"Vide" means &lt;em&gt;empty&lt;/em&gt; in French. And that's exactly what unstructured AI coding produces when the vibes run out.&lt;/p&gt;

&lt;p&gt;I run multiple &lt;a href="https://code.claude.com/docs" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; agents in parallel across git worktrees every day. I'm not here to tell you to stop using AI for development. But when a SaaStr founder &lt;a href="https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/" rel="noopener noreferrer"&gt;lost his production database&lt;/a&gt; to an AI coding agent in July 2025, it confirmed something I'd already learned: vibes alone aren't enough.&lt;/p&gt;

&lt;p&gt;The answer isn't less AI. It's more guidance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Piece: Guidance
&lt;/h2&gt;

&lt;p&gt;Claude Code is powerful, but out-of-the-box sessions are ephemeral — context evaporates between tasks, there's no trail of decisions, and commits happen whenever the AI feels like it.&lt;/p&gt;

&lt;p&gt;So I built a set of &lt;a href="https://github.com/couimet/my-claude-skills" rel="noopener noreferrer"&gt;custom skills&lt;/a&gt; — portable markdown instructions that Claude follows when you type &lt;code&gt;/skill-name&lt;/code&gt; in Claude Code (&lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;skills are a standard Claude Code extension mechanism&lt;/a&gt;). They live in &lt;code&gt;~/.claude/skills/&lt;/code&gt; — install once, use everywhere. They encode a simple contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Questions go to files, not chat.&lt;/strong&gt; &lt;code&gt;/question&lt;/code&gt; creates a structured document I edit directly — not a conversation that scrolls away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never implement before the plan is approved.&lt;/strong&gt; &lt;code&gt;/scratchpad&lt;/code&gt; saves plans to files I control. I iterate until I'm satisfied, and every block the AI tackles comes from a plan I've reviewed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never auto-commit.&lt;/strong&gt; &lt;code&gt;/commit-msg&lt;/code&gt; writes a draft file. I review and commit manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't complicated rules. But they're the difference between vide coding and what I call &lt;em&gt;vibe guiding&lt;/em&gt; — you steer the AI through a structured workflow instead of hoping it gets the next thing right.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Optional but useful: I also use RangeLink to navigate scratchpads with precise line references — it makes reviewing plans faster, but the skills work perfectly without it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9teygum186385s2l6ye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9teygum186385s2l6ye.png" alt="Skill workflow diagram as of March 2026" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Skills keep evolving — see the &lt;a href="https://github.com/couimet/my-claude-skills#see-it-in-action" rel="noopener noreferrer"&gt;latest workflow&lt;/a&gt; on GitHub.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I documented a &lt;a href="https://ouimet.info/follow-alongs/my-claude-skills-issues-10.html" rel="noopener noreferrer"&gt;full issue lifecycle&lt;/a&gt; — every artifact real, nothing fabricated. Here's the compressed version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;/start-issue&lt;/code&gt;&lt;/strong&gt; — I point Claude at a GitHub issue. It fetches the details, creates a branch, explores the codebase, and writes an implementation plan via &lt;code&gt;/scratchpad&lt;/code&gt; with concrete steps — each with its own status and defined interdependencies. Then it stops and waits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. I review the plan.&lt;/strong&gt; Sometimes I adjust scope. Sometimes I use &lt;code&gt;/question&lt;/code&gt; to surface design decisions in a structured file. The plan lives in a file I can read, edit, and come back to — not buried in a chat transcript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;/tackle-scratchpad-block&lt;/code&gt;&lt;/strong&gt; — I point Claude at one step or a set of steps from the plan. It executes them, runs tests, updates each step's status in the scratchpad, and writes a commit message draft. It does not commit. I review the diff, review the message, and commit when I'm satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Repeat&lt;/strong&gt; until all steps are done. Because steps have explicit interdependencies, independent ones can be tackled by parallel agents within the same worktree for faster throughput. One caveat: parallel agents may touch the same files across different tasks, so hand-picking staged blocks for truly atomic commits gets tricky — the practical trade-off is to embrace the parallelism and accept slightly larger commits.&lt;/p&gt;

&lt;p&gt;The scratchpad evolves as I iterate — I might spin off a new &lt;code&gt;/scratchpad&lt;/code&gt; with pros and cons to evaluate an approach, then integrate the decision back into the main plan. The thought process lives in files, not in my head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;/finish-issue&lt;/code&gt;&lt;/strong&gt; — Claude runs verification (lint, tests), checks if documentation needs updating, and generates a PR description. It does not create the PR. I review and submit.&lt;/p&gt;

&lt;p&gt;At every stage, I'm in the loop. The AI does the heavy lifting. I do the steering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;Skills are workflow steps with hard stops built in. Every skill produces a file I review before anything becomes permanent — plans, questions, commit messages, PR descriptions. Nothing reaches the repo without passing through my eyes first. That's what makes it vibe &lt;em&gt;guiding&lt;/em&gt;: the AI brings the force, the workflow lets me guide it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The skills are open source and designed to be portable — they work in any project via symlinks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:couimet/my-claude-skills.git ~/src/my-claude-skills
~/src/my-claude-skills/install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the full "show the work" follow-along: &lt;a href="https://ouimet.info/follow-alongs/my-claude-skills-issues-10.html" rel="noopener noreferrer"&gt;ouimet.info/follow-alongs/my-claude-skills-issues-10&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vide → Vibe Guiding
&lt;/h2&gt;

&lt;p&gt;Vibe coding is fun until the vibes run out. When they do, you're left with duplicated code instead of refactored, missing dependency injection, and untestable first drafts.&lt;/p&gt;

&lt;p&gt;But here's what I've found: once you give the AI proper guidance through structured skills, it &lt;em&gt;continues&lt;/em&gt; with that quality. Guide it toward dependency injection once, and it keeps using the pattern. Set up testable architecture in the first step, and every subsequent step follows suit.&lt;/p&gt;

&lt;p&gt;That's supercharged vibe guiding. You're not fighting the AI or replacing it. You're giving it rails to run on — and then it runs far.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inspired by &lt;a href="https://boristane.com/blog/how-i-use-claude-code/" rel="noopener noreferrer"&gt;Boris Tane's workflow article&lt;/a&gt;, this post was built with the same skills it describes — starting from &lt;a href="https://github.com/couimet/my-claude-skills/issues/20" rel="noopener noreferrer"&gt;issue #20&lt;/a&gt;. Over or under 10 skill invocations to go from blank file to what you're reading? Leave your guess below.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About RangeLink
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/couimet/rangeLink#rangelink" rel="noopener noreferrer"&gt;RangeLink&lt;/a&gt; is an extension I built to create precise code references for AI assistants. One keybinding. Any AI, any tool. Character-level precision. Available for &lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; and &lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>RangeLink v1.0.0: Perfected AI Workflows + The R-Keybinding Family</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Tue, 16 Dec 2025 01:52:33 +0000</pubDate>
      <link>https://dev.to/couimet/rangelink-v100-perfected-ai-workflows-the-r-keybinding-family-104b</link>
      <guid>https://dev.to/couimet/rangelink-v100-perfected-ai-workflows-the-r-keybinding-family-104b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff98z91jdfj0gthw2ketq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff98z91jdfj0gthw2ketq.png" alt="RangeLink Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-06-12):&lt;/strong&gt; RangeLink 2.0.0 has shipped. &lt;a href="https://dev.to/couimet/rangelink-200-bind-first-every-r-follows-11nd"&gt;Read the v2.0.0 release post.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey folks! &lt;strong&gt;RangeLink v1.0.0&lt;/strong&gt; is here, and this feels like a real milestone.&lt;/p&gt;

&lt;p&gt;If you've been following along since &lt;a href="https://dev.to/couimet/rangelink-v030-one-keybinding-to-rule-them-all-2h01"&gt;v0.3.0&lt;/a&gt;, you know RangeLink brings character-level precision to code references and seamless paste destinations for AI workflows — all with one keybinding that works regardless of which AI assistant you're using. &lt;strong&gt;v1.0.0 perfects that experience&lt;/strong&gt; by eliminating a major v0.3.0 pain point and expanding your choices with new features.&lt;/p&gt;

&lt;p&gt;This isn't just a version bump — it's a commitment that RangeLink is mature, reliable, and ready for your daily workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's New in v1.0.0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🎯 Perfected Paste Destinations (Major UX Improvement)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code Extension and Cursor AI now &lt;strong&gt;fully automatic&lt;/strong&gt; — no more manual Cmd+V paste!&lt;/li&gt;
&lt;li&gt;In v0.3.0, these destinations required you to manually paste after RangeLink copied the link. That workflow interruption is gone.&lt;/li&gt;
&lt;li&gt;All AI chat destinations now provide identical seamless UX: select code → link appears → keep typing.&lt;/li&gt;
&lt;li&gt;Remember "One Keybinding to Rule Them All"? &lt;strong&gt;In v1.0.0, it truly does.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🤖 GitHub Copilot Chat Integration (3rd AI Chat Option)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paste destination for GitHub Copilot Chat&lt;/li&gt;
&lt;li&gt;Expands your AI choices: Claude, Cursor AI, or Copilot — your preference, your workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⌨️ Complete R-Keybinding Family (New Commands)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;R-C&lt;/strong&gt;: Clipboard-only RangeLink generation (cross-project sharing with absolute paths, no destination unbind needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R-V&lt;/strong&gt;: Paste selected text directly to destination (not just links!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R-J&lt;/strong&gt;: Jump to bound destination (no more tab hunting)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🔄 Smart Bind with Confirmation (QoL)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick switching between destinations without manual unbind&lt;/li&gt;
&lt;li&gt;QuickPick dialog confirms before replacing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature Deep-Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The v0.3.0 → v1.0.0 Evolution: Fully Automatic AI Chats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem in v0.3.0:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code Extension and Cursor AI destinations worked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select code → &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;RangeLink copied link to clipboard and opened chat panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You manually pasted with Cmd+V&lt;/strong&gt; ← workflow interruption&lt;/li&gt;
&lt;li&gt;Continue typing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It worked, but that manual paste step broke the flow. Terminal and Text Editor destinations were already fully automatic, so the inconsistency was frustrating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution in v1.0.0:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All destinations now fully automatic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select code → &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Link appears in chat automatically&lt;/li&gt;
&lt;li&gt;Continue typing immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How I did it (the technical bit):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The lightbulb moment came from &lt;a href="https://github.com/couimet/rangeLink/pull/136" rel="noopener noreferrer"&gt;PR #136&lt;/a&gt; when improving the Terminal destination. I realized &lt;code&gt;executeCommand()&lt;/code&gt; could programmatically trigger actions in VSCode extensions. For Claude Code and Cursor AI (which don't expose text insertion APIs), RangeLink could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set focus to the chat panel (RangeLink was already doing this)&lt;/li&gt;
&lt;li&gt;Copy link to clipboard&lt;/li&gt;
&lt;li&gt;Add a small delay (50-150ms) to ensure focus is ready&lt;/li&gt;
&lt;li&gt;Execute the paste command programmatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's a clipboard-based workaround, but from the user's perspective it's indistinguishable from true API-based insertion. The workflow is seamless, and that's what matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Claude Code, Cursor AI, and Copilot Chat all provide identical automatic paste UX. Choose your AI based on preference, not workflow limitations. The v0.3.0 promise of "One Keybinding to Rule Them All" is now fully delivered.&lt;/p&gt;




&lt;h3&gt;
  
  
  The R-Keybinding Family: R-C, R-V, R-J
&lt;/h3&gt;

&lt;p&gt;All RangeLink commands start with &lt;code&gt;Cmd+R&lt;/code&gt; (for &lt;strong&gt;R&lt;/strong&gt;ange*&lt;em&gt;L&lt;/em&gt;*ink), creating a memorable pattern. v1.0.0 completes the R-family with three new commands (R-C, R-V, R-J):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keybinding&lt;/th&gt;
&lt;th&gt;Not About...&lt;/th&gt;
&lt;th&gt;Actually Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-L&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;ocket &lt;strong&gt;L&lt;/strong&gt;eague 🎮&lt;/td&gt;
&lt;td&gt;Generate RangeLink at current selection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;adio &lt;strong&gt;C&lt;/strong&gt;ontrol 📻&lt;/td&gt;
&lt;td&gt;Copy RangeLink to &lt;strong&gt;C&lt;/strong&gt;lipboard only (skip destination)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;ecreational &lt;strong&gt;V&lt;/strong&gt;ehicle 🚐&lt;/td&gt;
&lt;td&gt;Paste selected text directly to destination (like &lt;strong&gt;V&lt;/strong&gt; for paste)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R-J&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;R&lt;/strong&gt;oger &lt;strong&gt;J&lt;/strong&gt;unior&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;J&lt;/strong&gt;ump to your currently bound destination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  R-C: RangeLink Clipboard-Only Mode
&lt;/h4&gt;

&lt;p&gt;Sometimes you need a RangeLink but don't want it auto-pasting to your bound destination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharing links across projects or IDE instances&lt;/li&gt;
&lt;li&gt;Pasting into Slack, documentation, etc&lt;/li&gt;
&lt;li&gt;Validating references before sending to AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Cmd+R Cmd+C&lt;/code&gt;&lt;/strong&gt; generates a formatted RangeLink (e.g., &lt;code&gt;src/auth.ts#L42C10-L58C25&lt;/code&gt;) directly to clipboard, bypassing your bound destination entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key benefit:&lt;/strong&gt; No need to unbind your destination first. Generate clipboard-only links on demand while keeping your paste destination active.&lt;/p&gt;

&lt;p&gt;Supports both relative paths (same project) and absolute paths (cross-project sharing). Use &lt;code&gt;Cmd+R Cmd+Shift+C&lt;/code&gt; for absolute paths when sharing links across different projects or IDE instances.&lt;/p&gt;

&lt;h4&gt;
  
  
  R-V: Paste Text, Not Just Links 🚐
&lt;/h4&gt;

&lt;p&gt;You've bound RangeLink to your terminal or AI chat, and now you want to send actual code — not a link, but the &lt;strong&gt;selected text itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Cmd+R Cmd+V&lt;/code&gt;&lt;/strong&gt; sends your selected text directly to your bound destination. Same seamless workflow: select code → &lt;code&gt;R-V&lt;/code&gt; → text appears → destination auto-focuses → keep typing.&lt;/p&gt;

&lt;p&gt;Why the RV emoji? Well, &lt;code&gt;R-V&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; literally a Recreational Vehicle. I couldn't resist. 🚐&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Works with all destinations (Claude Code Extension, Cursor AI, GitHub Copilot Chat, Terminal, Text Editor) and handles multi-selection by concatenating with newlines. Perfect for quickly sharing code snippets with AI assistants — even if your current AI tool doesn't offer this feature natively.&lt;/p&gt;

&lt;h4&gt;
  
  
  R-J: Jump to Destination
&lt;/h4&gt;

&lt;p&gt;You've bound a terminal or text editor as your paste destination, but it's buried under other tabs or panes. Instead of hunting for it, hit &lt;strong&gt;&lt;code&gt;Cmd+R Cmd+J&lt;/code&gt;&lt;/strong&gt; to instantly jump to (and focus) your bound destination.&lt;/p&gt;

&lt;p&gt;Not about Roger Junior. Just a quick jump to where your links are going. 🎯&lt;/p&gt;




&lt;h3&gt;
  
  
  GitHub Copilot Chat: Your 3rd AI Chat Option
&lt;/h3&gt;

&lt;p&gt;v1.0.0 adds GitHub Copilot Chat support as a paste destination, giving you three AI chat options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code Extension&lt;/strong&gt; — Anthropic's official extension (works in VSCode and Cursor)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor AI&lt;/strong&gt; — Built into Cursor IDE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot Chat&lt;/strong&gt; — GitHub's AI coding assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three provide identical automatic paste UX. Choose based on your AI preference, not workflow constraints.&lt;/p&gt;

&lt;p&gt;Bind via Command Palette → "Bind RangeLink to GitHub Copilot Chat Destination"&lt;/p&gt;




&lt;h3&gt;
  
  
  Smart Bind with Confirmation
&lt;/h3&gt;

&lt;p&gt;Switching between destinations is now frictionless. Run any "Bind to..." command when already bound, and RangeLink shows a QuickPick dialog confirming the switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Currently bound to: Terminal
Switch to: Claude Code Chat?
[Yes] [No]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to unbind first—just quick switching with confirmation as your workflow demands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why RangeLink Matters
&lt;/h2&gt;

&lt;p&gt;Built-in AI features are convenient, but they lock you into one AI model, one workflow, and usually only line-level precision. RangeLink gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Character-level precision&lt;/strong&gt; — Highlight exactly the function signature, the problematic condition, that one sneaky semicolon. Not the whole block. Most AI code-sharing tools only work at line-level.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Any AI assistant&lt;/strong&gt; — Claude, GPT, Gemini, Copilot, whatever you prefer. No vendor lock-in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One keybinding memory&lt;/strong&gt; — Switch between Claude, Copilot, Cursor, or terminal assistants without relearning shortcuts. &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; works the same everywhere. Your muscle memory stays constant even as your AI toolkit evolves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Features your AI doesn't have&lt;/strong&gt; — &lt;code&gt;Cmd+R Cmd+V&lt;/code&gt; sends selected text directly to any AI chat. Even if your current tool doesn't offer this natively, RangeLink does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexible workflows&lt;/strong&gt; — Terminal for quick questions, scratchpad for complex prompts, direct AI chat integrations. All with the same seamless UX.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Universal format&lt;/strong&gt; — GitHub-style links that work everywhere (PRs, Slack, docs, teammates without RangeLink). RangeLinks aren't proprietary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And with v1.0.0, the workflow is just as seamless as integrated tools — arguably better, because you're not limited to one AI vendor or workflow pattern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install RangeLink:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;: &lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt;: &lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick start:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Command Palette → "Bind RangeLink to [your preferred destination]"&lt;/li&gt;
&lt;li&gt;Select code → Try the R-keybindings:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; — Generate RangeLink (auto-pastes to destination)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+R Cmd+C&lt;/code&gt; — Clipboard-only copy (no destination paste)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+R Cmd+V&lt;/code&gt; — Paste selected text directly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+R Cmd+J&lt;/code&gt; — Jump to bound destination&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cmd+Click any RangeLink in terminal or editor to navigate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The text editor destination with a split-screen scratchpad is still my favorite for complex AI prompts — lets you iterate on context before sending. Give it a try!&lt;/p&gt;




&lt;h2&gt;
  
  
  How Are You Using RangeLink?
&lt;/h2&gt;

&lt;p&gt;RangeLink v1.0.0 is feature-complete and stable. The core vision is realized: character-level precision, seamless AI workflows, flexible paste destinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'd love to hear about your usage patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which destinations do you use most? (Terminal? Scratchpad? AI chat?)&lt;/li&gt;
&lt;li&gt;How are the R-keybindings fitting into your workflow?&lt;/li&gt;
&lt;li&gt;Any rough edges or unexpected behaviors?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your feedback shapes priorities. If you're interested in contributing—code, docs, ideas—check out &lt;a href="https://github.com/couimet/rangeLink/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt; or open a discussion. The codebase is TypeScript with comprehensive test coverage, and PRs are always welcome.&lt;/p&gt;

&lt;p&gt;Built something cool with RangeLink? Share it! Always fun to see how people are using the tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;If RangeLink is useful for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star the repo&lt;/strong&gt; on &lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Report issues or share ideas&lt;/strong&gt; via &lt;a href="https://github.com/couimet/rangeLink/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Contribute&lt;/strong&gt; — TypeScript codebase with comprehensive test coverage, PRs welcome&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Share your workflows&lt;/strong&gt; — Drop a comment below or open a discussion&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink/blob/main/packages/rangelink-vscode-extension/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>cursor</category>
    </item>
    <item>
      <title>RangeLink v0.3.0: One Keybinding to Rule Them All</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Wed, 12 Nov 2025 04:00:54 +0000</pubDate>
      <link>https://dev.to/couimet/rangelink-v030-one-keybinding-to-rule-them-all-2h01</link>
      <guid>https://dev.to/couimet/rangelink-v030-one-keybinding-to-rule-them-all-2h01</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff98z91jdfj0gthw2ketq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff98z91jdfj0gthw2ketq.png" alt="RangeLink Logo" width="256" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-06-12):&lt;/strong&gt; RangeLink 2.0.0 has shipped. &lt;a href="https://dev.to/couimet/rangelink-200-bind-first-every-r-follows-11nd"&gt;Read the v2.0.0 release post.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey folks! Just shipped &lt;strong&gt;RangeLink v0.3.0&lt;/strong&gt;, and I'm genuinely excited about this one.&lt;/p&gt;

&lt;p&gt;If you caught my &lt;a href="https://dev.to/couimet/i-built-a-vs-code-extension-to-stop-the-copy-paste-madness-3d7l"&gt;previous post about v0.2.1&lt;/a&gt;, you know RangeLink started as a way to share precise code references with AI assistants in the terminal. That's still there, but v0.3.0 takes it further: &lt;strong&gt;one keybinding (&lt;code&gt;Cmd+R Cmd+L&lt;/code&gt;) now sends your code references anywhere you need them.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evolution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v0.2.0&lt;/strong&gt; launched with terminal binding — auto-send links to your integrated terminal where AI assistants can see them — plus clickable navigation to jump back to code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v0.3.0&lt;/strong&gt; introduces &lt;strong&gt;Paste Destinations&lt;/strong&gt; — a unified system that lets you bind RangeLink to wherever you're working: Claude Code Extension, Cursor AI, your terminal, or even a scratchpad file for drafting complex AI prompts.&lt;/p&gt;

&lt;p&gt;Same keybinding. Different destinations. Your choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Here's the thing about built-in AI features in editors: they're convenient, but they lock you into one AI model, one workflow, and usually only line-level precision. RangeLink gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Character-level precision&lt;/strong&gt; — Not just line 42, but &lt;code&gt;#L42C10-L58C25&lt;/code&gt; (that exact function signature, that specific condition)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any AI assistant&lt;/strong&gt; — Claude, GPT, Gemini, whatever you prefer. No vendor lock-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible workflows&lt;/strong&gt; — Terminal for quick questions, scratchpad for complex prompts, direct AI chat integrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal format&lt;/strong&gt; — GitHub-style links that work everywhere (PRs, Slack, docs, teammates without RangeLink)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? &lt;strong&gt;You don't give up any convenience.&lt;/strong&gt; Select code, hit &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt;, and your link appears exactly where you need it — with the same character-level precision that makes RangeLink special.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New in v0.3.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Paste Destinations (The Big One)
&lt;/h3&gt;

&lt;p&gt;Bind RangeLink to one destination at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code Extension&lt;/strong&gt; — Links open Claude's chat panel (works in VSCode and Cursor)*&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor AI&lt;/strong&gt; — Links open Cursor's AI chat*&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal&lt;/strong&gt; — Auto-paste links for terminal-based AI assistants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Editor&lt;/strong&gt; — Draft complex prompts in any file (markdown, untitled, whatever)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All destinations share the same seamless UX: select code → &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; → link appears at your cursor position → destination auto-focuses → keep typing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(*) &lt;strong&gt;FULL DISCLAIMER:&lt;/strong&gt; Claude Code Extension and Cursor AI destinations use a clipboard-based workaround because their APIs don't support programmatic text insertion yet (as of Nov 2025). RangeLink copies the link and opens the chat panel, but you need to paste (&lt;code&gt;Cmd+V&lt;/code&gt; / &lt;code&gt;Ctrl+V&lt;/code&gt;) yourself. Terminal and Text Editor destinations fully auto-paste without manual intervention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Editor Link Navigation
&lt;/h3&gt;

&lt;p&gt;Any RangeLink in any editor file (markdown, code, untitled) is now clickable. Hover to preview, Cmd+Click to navigate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; You're drafting a prompt in a scratchpad file with multiple code references. Before sending to your AI assistant, you can validate each link by clicking it — makes sure you're sharing the right context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "One Keybinding" Philosophy
&lt;/h3&gt;

&lt;p&gt;Every AI tool has its own way to share code — different shortcuts, different formats, different workflows.&lt;/p&gt;

&lt;p&gt;RangeLink unifies it: &lt;strong&gt;&lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; works everywhere&lt;/strong&gt;, with &lt;strong&gt;character-level precision everywhere&lt;/strong&gt;, and connects to &lt;strong&gt;any AI assistant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One keybinding to rule them all&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm Excited
&lt;/h2&gt;

&lt;p&gt;This release makes RangeLink competitive with integrated AI features without sacrificing its core strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're not locked into one AI model&lt;/li&gt;
&lt;li&gt;You get more precision (characters, not just lines)&lt;/li&gt;
&lt;li&gt;Links work universally (paste them anywhere, share with anyone)&lt;/li&gt;
&lt;li&gt;The workflow is just as seamless as built-in tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly? The paste destinations architecture feels like the right foundation for whatever comes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the Scenes: Working with AI on RangeLink
&lt;/h2&gt;

&lt;p&gt;One thing I've been experimenting with: using AI assistants to help build RangeLink itself. I've progressively added instructions to &lt;a href="https://github.com/couimet/rangeLink/blob/main/CLAUDE.md" rel="noopener noreferrer"&gt;CLAUDE.md&lt;/a&gt; to guide how Claude Code helps me develop.&lt;/p&gt;

&lt;p&gt;A pattern I really like is the &lt;a href="https://github.com/couimet/rangeLink/blob/80f9432b82121161b6e227febe1afc4924b9f541/CLAUDE.md?plain=1#L64-L99" rel="noopener noreferrer"&gt;questions template&lt;/a&gt;. When Claude needs design decisions before implementing a feature, instead of asking questions in the terminal (which gets messy), it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Saves questions to a &lt;code&gt;.txt&lt;/code&gt; file in &lt;code&gt;.claude-questions/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pre-fills recommended answers when it has context&lt;/li&gt;
&lt;li&gt;I edit the file with my decisions&lt;/li&gt;
&lt;li&gt;Claude reads my answers and proceeds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps the workflow clean and creates a record of design decisions. The questions file becomes documentation.&lt;/p&gt;

&lt;p&gt;If you're working with AI on your projects, this pattern might be worth trying!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install RangeLink:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;: &lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt;: &lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick start:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Command Palette → "Bind RangeLink to [your preferred destination]"&lt;/li&gt;
&lt;li&gt;Select code → &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; (or Command Palette → "Copy Range Link" if you have keybinding conflicts)&lt;/li&gt;
&lt;li&gt;Your link is ready where you need it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Try the text editor destination with a split-screen scratchpad — it's a game-changer for complex AI prompts.&lt;/p&gt;

&lt;p&gt;Would love to hear your feedback, especially if you're bouncing between different AI assistants!&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;If you find RangeLink useful, I'd love your support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star the repo&lt;/strong&gt; on &lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — it helps others discover it&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Report bugs or request features&lt;/strong&gt; via &lt;a href="https://github.com/couimet/rangeLink/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt; — I've started adding ideas there, not yet organized into a roadmap but wanted to share visibility on what's on my mind&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Contribute&lt;/strong&gt; — the codebase is well-documented and PR-friendly&lt;/li&gt;
&lt;li&gt;🗣️ &lt;strong&gt;Share your feedback&lt;/strong&gt; — I'm actively iterating based on what the community needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For vim/neovim users interested in building a plugin: the &lt;a href="https://github.com/couimet/rangeLink/tree/main/packages/rangelink-core-ts" rel="noopener noreferrer"&gt;core library is platform-agnostic&lt;/a&gt; and designed for multi-editor support. Would love to collaborate!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>vscode</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a VS Code Extension to Stop the Copy-Paste Madness</title>
      <dc:creator>Charles Ouimet</dc:creator>
      <pubDate>Sat, 08 Nov 2025 22:46:36 +0000</pubDate>
      <link>https://dev.to/couimet/i-built-a-vs-code-extension-to-stop-the-copy-paste-madness-3d7l</link>
      <guid>https://dev.to/couimet/i-built-a-vs-code-extension-to-stop-the-copy-paste-madness-3d7l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-06-12):&lt;/strong&gt; RangeLink 2.0.0 has shipped. &lt;a href="https://dev.to/couimet/rangelink-200-bind-first-every-r-follows-11nd"&gt;Read the v2.0.0 release post.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey devs! I just shipped v0.2.1 of &lt;a href="https://github.com/couimet/rangeLink/tree/main/packages/rangelink-vscode-extension" rel="noopener noreferrer"&gt;&lt;strong&gt;RangeLink&lt;/strong&gt;&lt;/a&gt;, a VS Code/Cursor extension that fixes something that's been driving me (and probably you) crazy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I use &lt;a href="https://www.claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; running in a terminal &lt;em&gt;inside&lt;/em&gt; Cursor daily. And the constant copy-pasting between terminal and editor? Exhausting.&lt;/p&gt;

&lt;p&gt;One day, after the hundredth copy-paste, I got frustrated and just tried something: I sent Claude a link like &lt;code&gt;src/path/file.rb#L42C10-L58C25&lt;/code&gt; pointing to a specific code snippet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It just worked.&lt;/strong&gt; No explanation needed. Claude understood immediately.&lt;/p&gt;

&lt;p&gt;That was the lightbulb moment: &lt;strong&gt;precise code references should be universal&lt;/strong&gt;. Not just for AI assistants, but for code reviews, documentation, team collaboration — anywhere developers share code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What RangeLink Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create precise code references in one keystroke.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select some code in VS Code/Cursor&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; (Mac) or &lt;code&gt;Ctrl+R Ctrl+L&lt;/code&gt; (Windows/Linux)&lt;/li&gt;
&lt;li&gt;Done! Link is in your clipboard: &lt;code&gt;src/path/file.rb#L42C10-L58C25&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The notation is GitHub-inspired, so your teammates already know it. They don't even need RangeLink installed to understand your links.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Killer Feature (v0.2.1)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Auto-paste to terminal&lt;/strong&gt; — this is where it gets good for Claude Code users (or any terminal-based workflow).&lt;/p&gt;

&lt;p&gt;One-time setup: bind your Claude Code terminal to RangeLink (Command Palette → "Bind Terminal"). Now every RangeLink you generate &lt;strong&gt;automatically appears in that terminal&lt;/strong&gt;. No copy-paste needed. Zero. None.&lt;/p&gt;

&lt;p&gt;Select code → hit the keybinding → the link is instantly in your terminal prompt, ready to send to Claude (or any AI assistant).&lt;/p&gt;

&lt;p&gt;The copy-paste workflow? Gone. You stay in flow. Even if you switch between multiple terminals, links always go to your bound terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; You can also &lt;strong&gt;Cmd+Click&lt;/strong&gt; (Mac) or &lt;strong&gt;Ctrl+Click&lt;/strong&gt; (Windows/Linux) any RangeLink in your terminal to jump straight back to that code. No more "wait, which file was that?" moments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You'll Love It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No more "around line 42"&lt;/strong&gt; — Share exact ranges with column precision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works everywhere&lt;/strong&gt; — Claude Code, VS Code, Cursor, GitHub, Slack, PRs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One keystroke&lt;/strong&gt; — &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; → link copied, done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible paths&lt;/strong&gt; — Workspace-relative or absolute paths, your choice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code reviews ("The bug is in &lt;code&gt;api/routes.ts#L215C8-L223C45&lt;/code&gt;")&lt;/li&gt;
&lt;li&gt;AI assistants (multi-file context in one prompt)&lt;/li&gt;
&lt;li&gt;Team collaboration (universal format everyone can use)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the Logo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8jjg15p3feosy5dahsp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8jjg15p3feosy5dahsp.png" alt="RangeLink Logo" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ever notice the chicken in the logo? That's a &lt;strong&gt;free-range&lt;/strong&gt; chicken. Because your code should roam free across editors, tools, and teams.&lt;/p&gt;

&lt;p&gt;The chains represent links — connections between developers, tools, and ideas.&lt;/p&gt;

&lt;p&gt;Look at the numbers in the range, &lt;strong&gt;precision matters&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install RangeLink:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;: &lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt;: &lt;a href="https://open-vsx.org/extension/couimet/rangelink-vscode-extension" rel="noopener noreferrer"&gt;Open VSX Registry&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select some code, hit &lt;code&gt;Cmd+R Cmd+L&lt;/code&gt; (or Command Palette → "Copy Range Link" if you have keybinding conflicts), and paste the link into Claude Code or Slack. See how it feels to never say "around line X" again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;If you find RangeLink useful, I'd love your support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star the repo&lt;/strong&gt; on &lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — it helps others discover it&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Report bugs or request features&lt;/strong&gt; via &lt;a href="https://github.com/couimet/rangeLink/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Contribute&lt;/strong&gt; — the codebase is well-documented and PR-friendly&lt;/li&gt;
&lt;li&gt;🗣️ &lt;strong&gt;Share your feedback&lt;/strong&gt; — I'm actively iterating based on what the community needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;vim&lt;/code&gt;/&lt;code&gt;neovim&lt;/code&gt; users interested in building a plugin: the &lt;a href="https://github.com/couimet/rangeLink/tree/main/packages/rangelink-core-ts" rel="noopener noreferrer"&gt;core library is platform-agnostic&lt;/a&gt; and designed for multi-editor support. Would love to collaborate!&lt;/p&gt;

&lt;p&gt;Curious about the implementation? Browse the &lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;source code on GitHub&lt;/a&gt; or reach out — I'm happy to chat about the architecture and design decisions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=couimet.rangelink-vscode-extension" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/couimet/rangeLink?tab=readme-ov-file#history" rel="noopener noreferrer"&gt;Full README with origin story&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>vscode</category>
      <category>cursor</category>
      <category>extensions</category>
    </item>
  </channel>
</rss>
