<?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: psychomafia.tiger</title>
    <description>The latest articles on DEV Community by psychomafia.tiger (@tigergethigher).</description>
    <link>https://dev.to/tigergethigher</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3855374%2Fa1d5b6fb-8b6b-4547-8d6b-86565564abcb.jpg</url>
      <title>DEV Community: psychomafia.tiger</title>
      <link>https://dev.to/tigergethigher</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tigergethigher"/>
    <language>en</language>
    <item>
      <title>A clone of our VS Code extension appeared on OpenVSX five days before we open-sourced the repo. It has 5,090 downloads and is using our GitHub OAuth app.</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Thu, 23 Apr 2026 03:08:39 +0000</pubDate>
      <link>https://dev.to/tigergethigher/a-clone-of-our-vs-code-extension-appeared-on-openvsx-five-days-before-we-open-sourced-the-repo-it-1mbh</link>
      <guid>https://dev.to/tigergethigher/a-clone-of-our-vs-code-extension-appeared-on-openvsx-five-days-before-we-open-sourced-the-repo-it-1mbh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Names (&lt;code&gt;fyltroven&lt;/code&gt;, &lt;code&gt;Mapkoforceps&lt;/code&gt;) are used because they are public on OpenVSX and GitHub. We are not claiming intent. Our OpenVSX takedown report is open as of writing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  We did not expect to find this
&lt;/h2&gt;

&lt;p&gt;We flipped the repo public yesterday. April 21, 10:29 UTC. A private repo the day before, an MIT license the day after, that kind of week.&lt;/p&gt;

&lt;p&gt;This morning we checked our OpenVSX listing and our extension was there twice.&lt;/p&gt;

&lt;p&gt;Ours: &lt;code&gt;gitchat.gitchat&lt;/code&gt;, 11,308 downloads, &lt;code&gt;verified: true&lt;/code&gt;. Published yesterday.&lt;/p&gt;

&lt;p&gt;The other one: &lt;code&gt;fyltroven.gitchat-fast-tool&lt;/code&gt;. 5,090 downloads. &lt;code&gt;unrelatedPublisher: true&lt;/code&gt;. Published April 16.&lt;/p&gt;

&lt;p&gt;Five days before we open-sourced. Our repository was still private on April 16. Nobody outside the team had the source. The clone was already live.&lt;/p&gt;

&lt;p&gt;We pulled its &lt;code&gt;package.json&lt;/code&gt; expecting a half-thorough rebrand. &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;publisher&lt;/code&gt;, &lt;code&gt;homepage&lt;/code&gt;, &lt;code&gt;bugs&lt;/code&gt;, &lt;code&gt;repository&lt;/code&gt; were all updated. One field was not. And the further we dug into the shipped bytes, the less the interesting question was how someone rebranded our code, and the more it became how they had our code at all.&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%2F7uaqq15z1udk05h154cp.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%2F7uaqq15z1udk05h154cp.png" alt="OpenVSX listing for fyltroven.gitchat-fast-tool showing orange unrelatedPublisher warning banner and 5,090 downloads" width="800" height="1333"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The fake listing on OpenVSX. Orange banner at top is OpenVSX's own &lt;code&gt;unrelatedPublisher: true&lt;/code&gt; flag.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Ours&lt;/th&gt;
&lt;th&gt;The clone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Namespace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitchat.gitchat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fyltroven.gitchat-fast-tool&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publisher verified&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenVSX flag&lt;/td&gt;
&lt;td&gt;normal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unrelatedPublisher: true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenVSX downloads&lt;/td&gt;
&lt;td&gt;11,308&lt;/td&gt;
&lt;td&gt;5,090&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;1.1.5&lt;/td&gt;
&lt;td&gt;1.0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published&lt;/td&gt;
&lt;td&gt;2026-04-21 10:29 UTC&lt;/td&gt;
&lt;td&gt;2026-04-16 15:29 UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both listings are public. Open either URL and you see what we saw.&lt;/p&gt;
&lt;h2&gt;
  
  
  The field they forgot
&lt;/h2&gt;

&lt;p&gt;The clone's &lt;code&gt;package.json&lt;/code&gt; from the OpenVSX CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://open-vsx.org/api/fyltroven/gitchat-fast-tool/1.0.5/file/package.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gitchat-fast-tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fyltroven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repository"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/Mapkoforceps/gitchat-fast-tool.git"&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;"homepage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/Mapkoforceps/gitchat-fast-tool#readme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bugs"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/Mapkoforceps/gitchat-fast-tool/issues"&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;"qna"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/GitchatSH/gitchat_extension/issues"&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;&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%2Fn5q7rss488vby8v7qfvk.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%2Fn5q7rss488vby8v7qfvk.png" alt="Fake package.json with qna URL highlighted in red, still pointing to GitchatSH/gitchat_extension" width="800" height="418"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every other metadata field got rebranded. &lt;code&gt;qna&lt;/code&gt; was left pointing at our repo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;qna&lt;/code&gt; URL points to our repository, the one we made public yesterday. Everything else is rebranded. This one they missed.&lt;/p&gt;

&lt;p&gt;At that point it was a copy, not a coincidence. The question was how deep.&lt;/p&gt;
&lt;h2&gt;
  
  
  Seven minutes
&lt;/h2&gt;

&lt;p&gt;GitHub's REST API for the clone's repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mapkoforceps/gitchat-fast-tool&lt;/span&gt;
  &lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-16T15:22:16Z&lt;/span&gt;
  &lt;span class="s"&gt;pushed_at&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;2026-04-16T15:24:58Z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenVSX for the listing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-16T15:29:02Z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo created 15:22. First push 15:24. Published to OpenVSX 15:29. Seven minutes total. Nobody writes a Socket.IO chat extension with a sidebar webview in seven minutes. The only thing you can do in that window is upload code you already had.&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%2Fzysp0d3mdplzckocvv8q.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%2Fzysp0d3mdplzckocvv8q.png" alt="GitHub commits page for Mapkoforceps/gitchat-fast-tool showing commits within minutes of repo creation" width="800" height="629"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Commit history on the clone's repo. Initial commit at 15:22, push at 15:24, OpenVSX publish at 15:29.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And our source was still private on April 16. It stayed private for five more days.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part we cannot explain
&lt;/h2&gt;

&lt;p&gt;The repository was on GitHub with visibility &lt;code&gt;private&lt;/code&gt; until April 21 at 10:29 UTC. The clone was already on OpenVSX on April 16 at 15:29 UTC. Between those two timestamps, someone had our code on their laptop, packaged, and live.&lt;/p&gt;

&lt;p&gt;We do not know how.&lt;/p&gt;

&lt;p&gt;What we have checked so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub access logs for the repo show only authorized team members.&lt;/li&gt;
&lt;li&gt;There is no mirror of the source, no CI artifact we accidentally made public, no leaked log that we have found.&lt;/li&gt;
&lt;li&gt;The project was not invisible before we shipped. We posted in &lt;code&gt;r/vscode&lt;/code&gt; this week describing what we were building. Anyone reading that thread would have known the project existed and roughly what it did. The thread did not include a source archive or a binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;r/vscode&lt;/code&gt; thread predates the clone. If you find it and check the timestamps you can draw your own line between points. We are not going to draw it for you, because we do not know the real answer and guessing in print would be unfair to whoever did or did not do this.&lt;/p&gt;

&lt;p&gt;What we can say is narrow. Our repo was private when the clone appeared. The clone's publisher had a packaged copy of our extension five days before the code was public. If anyone reading this has seen a pattern like it, on OpenVSX or anywhere else, we would like to hear about it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inside the &lt;code&gt;.vsix&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;OpenVSX lets anyone download the packaged extension. We pulled the clone's &lt;code&gt;.vsix&lt;/code&gt; (8.75 MB) and unzipped it.&lt;/p&gt;

&lt;p&gt;Brand assets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitchat_banner.png
gitchat_hook_cover.gif
gitchat_myrepo.png
gitchat_social_cover.png
gitchat_top people.png
gitchat_toprepo.png
feature-chat.png
feature-explore.png
feature-friends.png
feature-profile.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fa425i17ycees8v5r9cwj.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%2Fa425i17ycees8v5r9cwj.png" alt="Terminal output of ls -lh on unzipped clone .vsix showing gitchat_banner.png, gitchat_toprepo.png, feature-chat.png and other branded files" width="800" height="526"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Unzipped &lt;code&gt;.vsix&lt;/code&gt; folder. Filenames carry our brand prefix unchanged.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These are our image files, filename prefix and all. The clone did not rename them.&lt;/p&gt;

&lt;p&gt;The minified JS bundle (&lt;code&gt;dist/extension.js&lt;/code&gt;, 484 KB) was not rebuilt. &lt;code&gt;strings&lt;/code&gt; on it:&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="err"&gt;id:&lt;/span&gt;&lt;span class="s2"&gt;"gitchat.signIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;handler:...&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="err"&gt;id:&lt;/span&gt;&lt;span class="s2"&gt;"gitchat.messageUser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;handler:...&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="err"&gt;id:&lt;/span&gt;&lt;span class="s2"&gt;"gitchat.viewMyProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;handler:...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"Sign in to view your GitChat profile."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"Enter GitHub username to message"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; registers 15 command IDs under &lt;code&gt;gitchat.*&lt;/code&gt; (&lt;code&gt;gitchat.signIn&lt;/code&gt;, &lt;code&gt;gitchat.signOut&lt;/code&gt;, &lt;code&gt;gitchat.userMenu&lt;/code&gt;, etc). There is no reason for an extension called &lt;code&gt;gitchat-fast-tool&lt;/code&gt; under publisher &lt;code&gt;fyltroven&lt;/code&gt; to be registering commands in our namespace, other than that is what was on disk when they ran &lt;code&gt;vsce package&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.vsix&lt;/code&gt; also shipped a &lt;code&gt;.claude/settings.json&lt;/code&gt; with a PreToolUse hook for &lt;code&gt;docs/contributors/${USERNAME}.md&lt;/code&gt;. That is our internal contributor-doc workflow. You do not ship somebody else's Claude Code config file by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  It gets worse: the clone uses our backend
&lt;/h2&gt;

&lt;p&gt;At this point we thought we were looking at an IP issue. Then we opened the &lt;code&gt;contributes.configuration.properties&lt;/code&gt; block of the shipped &lt;code&gt;package.json&lt;/code&gt;:&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;"gitchat.apiUrl"&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;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api-dev.gitchat.sh/api/v1"&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;"gitchat.wsUrl"&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;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ws-dev.gitchat.sh"&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;"gitchat.githubClientId"&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;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ov23liXf7KFWwKzcOHE0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GitHub OAuth App Client ID for device flow authentication"&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;Those are our servers. That client ID is the OAuth app we built and registered under our team's GitHub account.&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%2Fvvkq44qn8qbanw8u946q.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%2Fvvkq44qn8qbanw8u946q.png" alt="Diagram of OAuth device flow: 5,090 users install the clone, authenticate via our OAuth app, traffic routes through our backend to our database" width="800" height="213"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;What happens when a user signs into the clone. Every arrow ends on our infrastructure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reading the rest of the bundle, what happens to a user who installs the clone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They install &lt;code&gt;fyltroven.gitchat-fast-tool&lt;/code&gt; from OpenVSX.&lt;/li&gt;
&lt;li&gt;They hit Sign In. The extension starts a GitHub device flow using our client ID.&lt;/li&gt;
&lt;li&gt;GitHub shows them a device-code screen for the OAuth app registered under our team. They grant permissions to that app.&lt;/li&gt;
&lt;li&gt;Their VS Code gets a token issued against our OAuth app.&lt;/li&gt;
&lt;li&gt;The extension calls &lt;code&gt;api-dev.gitchat.sh&lt;/code&gt; and opens a socket to &lt;code&gt;ws-dev.gitchat.sh&lt;/code&gt;. Our backend validates the token and creates or looks up the user record in our database.&lt;/li&gt;
&lt;li&gt;"GitChat Fast Tool" shows up in their sidebar. Every message they send lives on our servers under a token issued by our app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We did not sign off on any of this. We found out because we downloaded the &lt;code&gt;.vsix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One honest caveat. GitHub's OAuth device flow does have a known public-client-ID-impersonation issue (Praetorian writes about it). In our specific case the device flow is doing what it is specified to do, tokens are scoped by what the user clicks through, and the backend is ours so nothing exfiltrates past us. The awkward fact is that 5,090 people installed an extension from an unverified publisher, and that extension has been quietly running through our OAuth app and our database the whole time. Neither we nor they knew.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where did 5,090 installs come from
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google   "gitchat-fast-tool"       : no matches outside the listing
Reddit   search                    : no results
Twitter  search                    : no results
GitHub   repo stars                : 0
Blog posts, social media promotion : none found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5,090 installs with zero discoverable source. We do not have an answer. We are not calling anyone an install farm. We just cannot make 5,090 and zero line up.&lt;/p&gt;

&lt;p&gt;If you have seen this on OpenVSX before, or you know of anyone running detection on install-count anomalies for VS Code marketplaces, talk to us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The publisher
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;Mapkoforceps&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;blog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;(empty)&lt;/span&gt;
&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;followers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;2026-03-13T11:46:50Z&lt;/span&gt;
&lt;span class="na"&gt;public_repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F80zn9fnytnb2ndggn1qc.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%2F80zn9fnytnb2ndggn1qc.png" alt="GitHub profile page for Mapkoforceps: no avatar, no bio, 0 followers, 2 public repositories" width="800" height="548"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The GitHub profile that published the clone. Created 34 days before the publish, no profile info, no followers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two repos on that account. &lt;code&gt;oogymera&lt;/code&gt;, created the same day the account was, empty, zero stars. &lt;code&gt;gitchat-fast-tool&lt;/code&gt;, the clone.&lt;/p&gt;

&lt;p&gt;This is a thin profile by any standard. We are not attributing intent. We noticed the same signals that you probably noticed reading that block, and they are the reason we decided a cease-and-desist email would not go anywhere useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Filed an OpenVSX takedown on the &lt;code&gt;open-vsx.org&lt;/code&gt; issue tracker, citing the &lt;code&gt;unrelatedPublisher&lt;/code&gt; flag, the &lt;code&gt;qna&lt;/code&gt; URL mismatch, the identical minified bundle, and the backend config.&lt;/li&gt;
&lt;li&gt;Flipped our repo public on MIT at &lt;code&gt;github.com/GitchatSH/gitchat_extension&lt;/code&gt; (the evidence chain is in the commit history, so we wanted that public regardless).&lt;/li&gt;
&lt;li&gt;Started planning the OAuth client ID rotation and the user-migration that goes with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No email to the clone publisher. The profile above is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are asking
&lt;/h2&gt;

&lt;p&gt;Three questions, picked because they are things we genuinely do not know the answer to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One.&lt;/strong&gt; Our repo was private on April 16. How does a clone of a private repo show up on OpenVSX? If you have seen a clone appear before the source went public, we would like to hear it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two.&lt;/strong&gt; 5,090 downloads and zero footprint. If this shape is familiar to anyone, tell us what you think happened and how you would check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three.&lt;/strong&gt; If you ship an open-source VS Code extension with a backend, your config defaults become part of a public surface you cannot take back. We knew that about our own release. We did not expect the defaults to be absorbed by a rebranded listing with five thousand downloads, five days before we shipped the source. If you have seen config-inheritance like this in a cloned extension, share the details.&lt;/p&gt;

&lt;p&gt;We are the GitChat team. Our real extension is &lt;code&gt;gitchat.gitchat&lt;/code&gt; on OpenVSX and &lt;code&gt;GitchatSH.gitchat&lt;/code&gt; on the VS Code Marketplace. Source at &lt;code&gt;github.com/GitchatSH/gitchat_extension&lt;/code&gt; on MIT. Comments are on.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
      <category>vscode</category>
      <category>security</category>
    </item>
    <item>
      <title>How We Built a Chat Layer for GitHub Right Inside VS Code</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Fri, 17 Apr 2026 08:23:16 +0000</pubDate>
      <link>https://dev.to/tigergethigher/how-we-built-a-chat-layer-for-github-right-inside-vs-code-1850</link>
      <guid>https://dev.to/tigergethigher/how-we-built-a-chat-layer-for-github-right-inside-vs-code-1850</guid>
      <description>&lt;p&gt;Pair-coding with someone in the same repo, every chat ping forces an alt-tab. Discord on one monitor. Slack on another. PR comments in a browser tab. Then you come back to the editor and the thing you were about to type is already half-gone from your head.&lt;/p&gt;

&lt;p&gt;We got tired of it. We shipped GitChat, a VS Code extension that puts the chat panel where the code lives. No new account, no separate app. Here is how we built it, and a few things we had to throw away along the way.&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%2Fvsm5t4d93ub5jdcate7n.gif" 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%2Fvsm5t4d93ub5jdcate7n.gif" alt=" " width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The alt-tab tax
&lt;/h2&gt;

&lt;p&gt;You are deep in a PR review. Something in the diff does not make sense. You want to ask the contributor a clarifying question. That is when the ritual kicks in. Cmd+Tab to Slack. Type the question. Wait. Read the reply. Cmd+Tab back. Whatever you were about to change is already half-remembered. Context is fraying before you type a single character.&lt;/p&gt;

&lt;p&gt;Multiply that by a normal day of reviewing code with two or three other people, and every conversation taxes your focus twice: once on the way out, once on the way back. The fix we wanted was not a better chat app. It was a chat app that lives in the same window as the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built it inside the editor and not as a web app
&lt;/h2&gt;

&lt;p&gt;The obvious question is why bother with an extension at all. Why not a web app with a keyboard shortcut?&lt;/p&gt;

&lt;p&gt;Three reasons pushed us toward the extension path.&lt;/p&gt;

&lt;p&gt;First, friction. Installing an extension is one command: &lt;code&gt;ext install GitchatSH.gitchat&lt;/code&gt;. No separate signup, no separate window.&lt;/p&gt;

&lt;p&gt;Second, attention. Developers already have VS Code, Cursor, Windsurf, or Antigravity open for hours. A web app is a tab, and a tab gets closed. An extension lives where the work happens.&lt;/p&gt;

&lt;p&gt;Third, identity. GitHub is already where developers have a profile, a follow graph, and a session token. An IDE extension can reuse that session the moment a user signs in. A web app has to rebuild it from scratch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quick note before we show code: the GitChat repo is not public yet; source-available is on the roadmap. The snippets below are real code from our repo, paraphrased for clarity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The auth we settled on: VS Code session first, GitHub Device Flow as fallback
&lt;/h2&gt;

&lt;p&gt;VS Code ships a built-in GitHub authentication provider, and most of the time it is the right choice for an extension. No redirect URI to configure, no client secret to protect, and the token is scoped the way the user expects.&lt;/p&gt;

&lt;p&gt;Our primary path is a single call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/index.ts (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GITHUB_SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read:user&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="s2"&gt;user:email&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="s2"&gt;user:follow&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="s2"&gt;repo&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GITHUB_SCOPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createIfNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// session.accessToken is ready to use&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For environments where the built-in provider is not available (some forks, some air-gapped setups), we fall back to the &lt;a href="https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events" rel="noopener noreferrer"&gt;GitHub Device Flow&lt;/a&gt;. We POST to &lt;code&gt;https://github.com/login/device/code&lt;/code&gt;, show the short user code in a VS Code notification, open the browser to the verification URL, and poll &lt;code&gt;/login/oauth/access_token&lt;/code&gt; until the user approves.&lt;/p&gt;

&lt;p&gt;Either way, the token lands in VS Code &lt;code&gt;SecretStorage&lt;/code&gt;. Not &lt;code&gt;globalState&lt;/code&gt;. Not a file on disk. If you are building a VS Code extension that handles any credential, this is the one API call that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your friends list is already on GitHub
&lt;/h2&gt;

&lt;p&gt;We deliberately did not ask users to build a new social graph. GitHub already has one.&lt;/p&gt;

&lt;p&gt;When you sign into GitChat, we query &lt;code&gt;GET /user/following&lt;/code&gt; and &lt;code&gt;GET /user/followers&lt;/code&gt;, intersect the two lists, and the set of mutual follows becomes your friends list. Zero manual adds. No invite links. If you already follow each other on GitHub, you can already message each other in the editor.&lt;/p&gt;

&lt;p&gt;The same identity powers the rest of the product. Every user card we render can pull fresh GitHub stats on demand, because the session token already has the scope for it.&lt;/p&gt;

&lt;p&gt;This also sets the table for what is coming next. When Team Channels ship, the same identity that powers your friends list drops you into a room with the maintainers and contributors of any repo you have committed to. No separate invite. Your commit history is the access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realtime with Socket.IO and a 30-second heartbeat
&lt;/h2&gt;

&lt;p&gt;For chat and presence we run a Socket.IO client inside the extension host. We chose &lt;code&gt;socket.io-client&lt;/code&gt; over a raw WebSocket for two reasons: auto-reconnect with exponential backoff is built in, and named events map cleanly to our server-side rooms (per-DM, per-group, per-repo).&lt;/p&gt;

&lt;p&gt;The presence protocol was where we spent the most tuning time. Too aggressive and the server melts under ping load. Too lazy and friends show "offline" mid-keystroke. We landed on a 30-second heartbeat against a 90-second backend TTL, which gives every client one full retry window before it gets marked offline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/realtime/index.ts (simplified)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;socket.io-client&lt;/span&gt;&lt;span class="dl"&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;PRESENCE_HEARTBEAT_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;reconnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reconnectionAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reconnectionDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reconnectionDelayMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connect&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_heartbeatTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_socket&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;presence:heartbeat&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;PRESENCE_HEARTBEAT_INTERVAL_MS&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 thing worth calling out is &lt;code&gt;reconnectionAttempts: Infinity&lt;/code&gt;. A developer flight-mode-ing on a plane, or closing the laptop for lunch, is the common case. Giving up after N tries would just punish the user on return.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is live, what is next
&lt;/h2&gt;

&lt;p&gt;We ship fast. Here is where things stand right now and what is on deck.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;DM and Group Chat&lt;/td&gt;
&lt;td&gt;Message anyone you follow on GitHub. Create groups with mutual friends.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;Friends and Presence&lt;/td&gt;
&lt;td&gt;Your mutual follows become your friends list. See who is coding now.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;Developer Profiles&lt;/td&gt;
&lt;td&gt;GitHub stats, top repos, and bio in a single card.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soon&lt;/td&gt;
&lt;td&gt;Community Channels&lt;/td&gt;
&lt;td&gt;Star a repo and join its community room.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soon&lt;/td&gt;
&lt;td&gt;Team Channels&lt;/td&gt;
&lt;td&gt;Contribute to a repo and get added to the team room.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soon&lt;/td&gt;
&lt;td&gt;Wave&lt;/td&gt;
&lt;td&gt;Ping an online friend with one tap.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  GitChat vs Live Share vs Copilot Chat
&lt;/h2&gt;

&lt;p&gt;People keep asking how this compares to Live Share and Copilot Chat, so here is the honest map. They solve different problems. Pick whichever one matches what you want to do.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GitChat&lt;/th&gt;
&lt;th&gt;VS Live Share&lt;/th&gt;
&lt;th&gt;Copilot Chat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it solves&lt;/td&gt;
&lt;td&gt;Human-to-human chat&lt;/td&gt;
&lt;td&gt;Real-time collaborative editing&lt;/td&gt;
&lt;td&gt;AI coding assistance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;Your GitHub account&lt;/td&gt;
&lt;td&gt;Live Share account&lt;/td&gt;
&lt;td&gt;Copilot subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;DMs, groups, repo channels&lt;/td&gt;
&lt;td&gt;One shared editing session&lt;/td&gt;
&lt;td&gt;Chat with AI about code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to use it&lt;/td&gt;
&lt;td&gt;Talk to the person you code with&lt;/td&gt;
&lt;td&gt;Pair-program live on one file&lt;/td&gt;
&lt;td&gt;Ask for code suggestions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Live Share and Copilot Chat both solve real problems. We just needed a fourth thing that none of them do: text conversation with the actual human on the other end of a follow graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in 30 seconds
&lt;/h2&gt;

&lt;p&gt;One command, then sign in with GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ext &lt;span class="nb"&gt;install &lt;/span&gt;GitchatSH.gitchat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open VS Code, Cursor, Windsurf, or Antigravity.&lt;/li&gt;
&lt;li&gt;Press Cmd+P / Ctrl+P and paste the command above.&lt;/li&gt;
&lt;li&gt;Click the GitChat icon in the activity bar.&lt;/li&gt;
&lt;li&gt;Sign in with GitHub. Your mutual follows appear as your friends list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few honest notes before you install. The GitChat repo is not public yet; source-available is on the roadmap. Messages go through our backend over HTTPS; end-to-end encryption is not shipped yet, so do not put production secrets in a DM. The extension works best when the people you want to talk to install it too.&lt;/p&gt;

&lt;p&gt;If it is useful, that is great. If something is broken, reply in the comments. We read everything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=GitchatSH.gitchat" rel="noopener noreferrer"&gt;Install from the VS Code Marketplace&lt;/a&gt; · &lt;a href="https://open-vsx.org/extension/Gitchat/gitchat" rel="noopener noreferrer"&gt;Open VSX&lt;/a&gt; · &lt;a href="https://gitchat.sh" rel="noopener noreferrer"&gt;gitchat.sh&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>webdev</category>
      <category>github</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to run Claude Code, Gemini, and Codex side-by-side from Discord</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Wed, 08 Apr 2026 06:27:02 +0000</pubDate>
      <link>https://dev.to/tigergethigher/how-to-run-claude-code-gemini-and-codex-side-by-side-from-discord-55a4</link>
      <guid>https://dev.to/tigergethigher/how-to-run-claude-code-gemini-and-codex-side-by-side-from-discord-55a4</guid>
      <description>&lt;p&gt;Last week I had Claude Code running a refactor in one terminal tab, Codex generating boilerplate in another, and Gemini reviewing a PR in a third. I alt-tabbed to check on Claude Code and it had been waiting for a permission prompt for 20 minutes. Codex had finished and I didn't notice. Gemini was halfway through but I couldn't tell from the tab title.&lt;/p&gt;

&lt;p&gt;Three agents, three terminal windows, zero visibility when I'm not staring at them.&lt;/p&gt;

&lt;p&gt;Claude Code is great for complex multi-file work but burns tokens on simple tasks. Codex is faster and cheaper for straightforward changes. Gemini handles code reviews well. I wanted to use all three, but managing them in separate terminals wasn't working. Especially when I step away from my desk.&lt;/p&gt;

&lt;p&gt;I set up OpenACP to run all three from Discord. My team already uses it, and threads keep each agent session separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this setup looks like
&lt;/h2&gt;

&lt;p&gt;OpenACP is a self-hosted bridge that connects AI coding agents to messaging platforms. For Discord specifically, it creates a forum channel where each agent session gets its own thread. You can run Claude Code in one thread, Codex in another, and Gemini in a third, all at the same time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discord Server
└── #openacp-sessions (forum channel)
    ├── Fix auth middleware race condition   (Claude Code)
    ├── Add user endpoint scaffolding        (Codex)
    └── Review PR #42 error handling         (Gemini)
└── #openacp-notifications
    └── Completion alerts with links back to threads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each thread shows real-time streaming: file reads, code writes, terminal commands, all as they happen. When an agent needs to write a file or run a command, Allow and Reject buttons show up in the thread. I approve from my phone and the agent continues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Discord
&lt;/h2&gt;

&lt;p&gt;The Discord setup takes about 10 minutes. You need a server where you have Manage Server permission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a bot on the Discord Developer Portal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to discord.com/developers/applications, create a new application, then go to Bot in the sidebar. Reset the token and copy it immediately. Under Privileged Gateway Intents, enable Message Content Intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Generate an invite URL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to OAuth2 → URL Generator. Check &lt;code&gt;bot&lt;/code&gt; and &lt;code&gt;applications.commands&lt;/code&gt; under Scopes. Under Bot Permissions, check: Manage Channels, Send Messages, Send Messages in Threads, Create Public Threads, Manage Threads, Manage Messages, Embed Links, Attach Files, Read Message History, Use Slash Commands, Add Reactions.&lt;/p&gt;

&lt;p&gt;Or just use this permission integer in the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;https://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&amp;amp;scope=bot+applications.commands&amp;amp;permissions=328565073936
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_APP_ID&lt;/code&gt; with your Application ID from the portal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Invite the bot and grab the Guild ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the invite URL, select your server, authorize. Then enable Developer Mode in Discord settings (User Settings → Advanced), right-click your server name, copy the Server ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Install and configure OpenACP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup wizard asks which platform you want. Pick Discord, paste the bot token, paste the Guild ID. It validates both and saves the config.&lt;/p&gt;

&lt;p&gt;Start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenACP creates two channels automatically: &lt;code&gt;#openacp-sessions&lt;/code&gt; (a forum channel for sessions) and &lt;code&gt;#openacp-notifications&lt;/code&gt; (a text channel for completion alerts). The bot registers slash commands within seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing multiple agents
&lt;/h2&gt;

&lt;p&gt;By default OpenACP uses Claude Code. To add more agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp agents &lt;span class="nb"&gt;install &lt;/span&gt;gemini
openacp agents &lt;span class="nb"&gt;install &lt;/span&gt;codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These pull from the ACP registry. You can see everything available with &lt;code&gt;openacp agents&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running agents side-by-side
&lt;/h2&gt;

&lt;p&gt;Once you have multiple agents installed, open your Discord server and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/new claude ~/projects/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new forum thread opens. It starts as "🔄 claude — New Session" while it initializes. Once you send your first message and the agent responds, it auto-renames to a short summary of the task, something like "Fix auth middleware race condition".&lt;/p&gt;

&lt;p&gt;While that's running, open a new thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/new codex ~/projects/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Codex is working on the same project in a separate thread. You can give it a different task. Both agents run in parallel, each in their own isolated session.&lt;/p&gt;

&lt;p&gt;Need a code review? Third thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/new gemini ~/projects/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three threads, three agents, one Discord server. Each one streams independently. When a session finishes, a completion notification goes to &lt;code&gt;#openacp-notifications&lt;/code&gt; with a link back to the thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the thread UI works
&lt;/h2&gt;

&lt;p&gt;Each thread shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool call embeds&lt;/strong&gt; with a colored sidebar. Blue while the agent is working, green when done, red on error. These group all the file reads, writes, grep calls, and terminal commands into a single embed so the thread doesn't flood with individual messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action row buttons&lt;/strong&gt; below the embed: &lt;code&gt;Low&lt;/code&gt;, &lt;code&gt;Medium&lt;/code&gt;, &lt;code&gt;High&lt;/code&gt; to control how much detail you see, and &lt;code&gt;Cancel&lt;/code&gt; to abort. No slash commands needed for the common actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission buttons&lt;/strong&gt; when the agent wants to do something destructive. Allow or Reject, right there in the thread. If you're on your phone and the agent wants to run &lt;code&gt;rm -rf node_modules &amp;amp;&amp;amp; npm install&lt;/code&gt;, you see the full command and can decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-naming&lt;/strong&gt; after the first prompt. The thread renames from "🔄 claude — New Session" to a short summary of the task, like "Fix auth middleware race condition".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Continuing or changing agents
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/newchat&lt;/code&gt; starts a fresh session in the same thread with the same agent and workspace. Useful when you want a clean slate without losing the conversation history above.&lt;/p&gt;

&lt;p&gt;To use a different agent, run &lt;code&gt;/new codex ~/projects/backend&lt;/code&gt; from any channel. It opens a fresh thread. There's no way to swap agents inside an existing thread, which honestly keeps things cleaner. Each thread is one agent, one task.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow I use daily
&lt;/h2&gt;

&lt;p&gt;Morning. I have a PR to review and two bugs to fix.&lt;/p&gt;

&lt;p&gt;Thread 1: &lt;code&gt;/new gemini ~/projects/api&lt;/code&gt;. I send "Review the diff on branch feature/notifications, focus on error handling." Gemini reads the diff, posts comments in the thread. I read them on my phone over coffee.&lt;/p&gt;

&lt;p&gt;Thread 2: &lt;code&gt;/new claude ~/projects/api&lt;/code&gt;. I send "The webhook handler in src/webhooks/stripe.ts is silently swallowing errors. Fix it and add proper logging." Claude Code digs through the codebase, finds the issue, proposes a fix. I tap Allow when it wants to write the file.&lt;/p&gt;

&lt;p&gt;Thread 3: &lt;code&gt;/new codex ~/projects/api&lt;/code&gt;. I send "Add a GET endpoint at /api/v1/health that returns the server uptime and version." Simple task, Codex handles it in under a minute.&lt;/p&gt;

&lt;p&gt;All three finish at different times. Each one posts a completion notification to &lt;code&gt;#openacp-notifications&lt;/code&gt;. I check the results when I'm back at my desk, or from my phone if I'm still out.&lt;/p&gt;

&lt;p&gt;Total cost for the morning: usually under $5. OpenACP tracks per-session token usage and cost, and you can set monthly budget limits that pause the agent when hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slash commands reference
&lt;/h2&gt;

&lt;p&gt;These register automatically when OpenACP starts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/new [agent] [workspace]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a new session in a new thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/newchat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New session with same agent and workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/cancel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancel the session in the current thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show session or system status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all active sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/menu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open the control panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/agents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browse available agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/install [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Install an agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/handoff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a terminal command to continue locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dangerous&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle auto-approve for all permission requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`/outputmode [low&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}`/tts [on&lt;/td&gt;
&lt;td&gt;off]`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/doctor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run system diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show all commands&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Things to know
&lt;/h2&gt;

&lt;p&gt;Everything except the model API calls stays on your machine. No relay, no third-party storing your sessions. Swapping between agents doesn't change your messaging setup.&lt;/p&gt;

&lt;p&gt;Some things I ran into. The forum channel works best with Discord Community mode enabled. Without it, OpenACP falls back to a regular text channel with threads, which is less organized. Discord's rate limits on message edits mean streaming output sometimes lags a couple seconds behind the actual agent. And if you're running three agents simultaneously, you're paying three sets of API costs, so the budget limit feature is worth setting up early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick Discord during setup. The wizard validates your bot token, connects to your server, and creates the channels. Takes about 10 minutes total.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;https://github.com/Open-ACP/OpenACP&lt;/a&gt;&lt;br&gt;
Discord setup guide: &lt;a href="https://openacp.gitbook.io/docs/platform-setup/discord" rel="noopener noreferrer"&gt;https://openacp.gitbook.io/docs/platform-setup/discord&lt;/a&gt;&lt;br&gt;
ACP registry (browse agents): &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;https://agentclientprotocol.com/get-started/registry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. I'm a contributor to OpenACP. Happy to answer questions about the multi-agent setup or Discord integration below.&lt;/p&gt;

</description>
      <category>discord</category>
      <category>ai</category>
      <category>opensource</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>How OpenACP talks to 28+ AI agents through one protocol — architecture of a self-hosted messaging bridge</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Mon, 06 Apr 2026 09:16:46 +0000</pubDate>
      <link>https://dev.to/tigergethigher/what-is-agent-client-protocol-acp-and-why-it-matters-for-developer-tools-5ck5</link>
      <guid>https://dev.to/tigergethigher/what-is-agent-client-protocol-acp-and-why-it-matters-for-developer-tools-5ck5</guid>
      <description>&lt;p&gt;I needed Claude Code to talk to Telegram. Not a chatbot — the actual coding agent, running on my machine, reading my files, writing code. I wanted to send it a task from my phone and get structured updates back, including permission prompts I could approve with a tap.&lt;/p&gt;

&lt;p&gt;Parsing terminal stdout wasn't going to cut it. I tried that approach early on and it kept breaking whenever the agent's output format changed.&lt;/p&gt;

&lt;p&gt;That's when I found &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;OpenACP&lt;/a&gt; and started contributing. What made it work where my hacky approach didn't was a protocol layer underneath: the Agent Client Protocol (ACP), an open standard that lets any client communicate with any compatible coding agent through structured JSON-RPC messages.&lt;/p&gt;

&lt;p&gt;Here's how the architecture works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: agents live in your terminal
&lt;/h2&gt;

&lt;p&gt;AI coding agents — Claude Code, Gemini CLI, Codex, and others — are powerful but they assume you're sitting at a terminal. Leave your desk and the agent hits a permission prompt? It waits. You come back 40 minutes later to find it's been idle the whole time.&lt;/p&gt;

&lt;p&gt;The workarounds people try: SSH into a tmux session on mobile (fragile), VPN + remote desktop (slow), or just hope nothing needs approval. None of these are actually good.&lt;/p&gt;

&lt;p&gt;What you want is a structured bridge: your messaging app on one side, the coding agent on the other, with a session manager in between that handles streaming, permissions, and multiple agents at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenACP's three layers
&lt;/h2&gt;

&lt;p&gt;OpenACP is a self-hosted bridge between messaging platforms (Telegram, Discord, Slack) and AI coding agents. Everything runs on your machine. The architecture has three layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────┐
│                    Your Phone                       │
│            Telegram / Discord / Slack               │
└──────────────────────┬─────────────────────────────┘
                       │
          Platform API (grammY / discord.js)
                       │
┌──────────────────────▼─────────────────────────────┐
│              Layer 1: Adapter                       │
│  Translates platform messages to internal events    │
│  Handles: buttons, topics/threads, streaming,       │
│  rate limiting, message chunking                    │
└──────────────────────┬─────────────────────────────┘
                       │
┌──────────────────────▼─────────────────────────────┐
│           Layer 2: Session Bridge                   │
│  Routes messages between adapters and agents        │
│  Manages: session lifecycle, prompt queue,          │
│  permission gates, cost tracking, parallel sessions │
└──────────────────────┬─────────────────────────────┘
                       │
            ACP protocol (JSON-RPC over stdio)
                       │
┌──────────────────────▼─────────────────────────────┐
│         Layer 3: Agent Connection                   │
│  Spawns agents as subprocesses, speaks ACP          │
│  Handles: initialize, session/new, session/prompt,  │
│  event streaming, permission relay                  │
└────────────────────────────────────────────────────┘
                       │
              Claude Code / Gemini CLI / Codex / ...
                       │
                  Your Codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer only talks to its neighbors. The adapter doesn't know which agent is running. The agent doesn't know whether it's talking to Telegram or Discord. The session bridge is the glue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: how agents actually communicate
&lt;/h2&gt;

&lt;p&gt;This is the part that was hardest to get right without a protocol.&lt;/p&gt;

&lt;p&gt;OpenACP spawns each agent as a subprocess and communicates over stdin/stdout using the Agent Client Protocol. ACP is built on JSON-RPC 2.0 — structured messages, not raw text.&lt;/p&gt;

&lt;p&gt;The lifecycle has three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Initialize&lt;/strong&gt; — OpenACP tells the agent what capabilities the client supports (file operations, terminal access) and the agent responds with what it can do:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&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;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientCapabilities"&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;"fs"&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;"readTextFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"writeTextFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"terminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;&lt;strong&gt;2. Create session&lt;/strong&gt; — Point the agent at a working directory:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session/new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&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;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/my-project"&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;&lt;strong&gt;3. Prompt&lt;/strong&gt; — Send a task. The agent streams back typed events as it works:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session/prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&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;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fix the auth bug in middleware.ts"&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;While working, the agent sends back events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent_message_chunk&lt;/code&gt; — the agent's reasoning, streamed as it thinks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_call&lt;/code&gt; / &lt;code&gt;tool_call_update&lt;/code&gt; — file reads, writes, grep, terminal commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;usage_update&lt;/code&gt; — token counts and cost&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plan&lt;/code&gt; — the agent's internal task plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the critical one: &lt;code&gt;request_permission&lt;/code&gt;. When the agent wants to do something that requires approval — write a file, run a destructive command — it sends a structured permission request. OpenACP turns that into a Telegram button or Discord interaction. The agent blocks until you respond.&lt;/p&gt;

&lt;p&gt;This is why the protocol matters. Without it, you're parsing terminal output trying to figure out if the agent is asking a question or just printing something. With ACP, a permission request is an explicit method call with structured options. There's no ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one protocol means 28+ agents
&lt;/h2&gt;

&lt;p&gt;Here's the real payoff.&lt;/p&gt;

&lt;p&gt;Because OpenACP speaks ACP, it works with every agent that also speaks ACP. The &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;ACP registry&lt;/a&gt; lists 28+ agents right now — Claude Code, Gemini CLI, Codex CLI, GitHub Copilot, Cursor, Cline, goose, Junie, Qwen Code, and more.&lt;/p&gt;

&lt;p&gt;Adding a new agent to OpenACP means registering it, not building a custom integration. When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp agents &lt;span class="nb"&gt;install &lt;/span&gt;gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenACP pulls the agent definition from the registry, resolves its distribution (npm, pip, or binary), and installs it. From that point on, you can spin up a Gemini session alongside your Claude Code session, each in its own Telegram topic or Discord thread.&lt;/p&gt;

&lt;p&gt;The typical setup: Claude Code running a longer task in one Telegram topic while you ask Gemini something quick in another. Same bridge, same interface, different agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick note: ACP is not MCP
&lt;/h2&gt;

&lt;p&gt;This confused me at first. Model Context Protocol (MCP) connects an AI model to tools — databases, APIs, browser automation. Agent Client Protocol (ACP) connects a user interface to a coding agent — prompts, streaming, permissions, sessions.&lt;/p&gt;

&lt;p&gt;They're complementary layers. Claude Code uses MCP internally to talk to tool servers, and speaks ACP externally to talk to whatever client is driving it — VS Code, a terminal, or OpenACP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a session looks like end-to-end
&lt;/h2&gt;

&lt;p&gt;When I send "Fix the auth bug in middleware.ts" to my Telegram bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Telegram adapter&lt;/strong&gt; receives the message, identifies the session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session bridge&lt;/strong&gt; queues the prompt, routes it to the right agent instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent connection&lt;/strong&gt; sends &lt;code&gt;session/prompt&lt;/code&gt; to Claude Code via ACP&lt;/li&gt;
&lt;li&gt;Claude Code reads files → comes back as &lt;code&gt;tool_call&lt;/code&gt; events → streamed to Telegram&lt;/li&gt;
&lt;li&gt;Claude Code greps the codebase → &lt;code&gt;tool_call&lt;/code&gt; event → streamed&lt;/li&gt;
&lt;li&gt;Claude Code wants to write a fix → &lt;code&gt;request_permission&lt;/code&gt; → Approve/Deny button in Telegram&lt;/li&gt;
&lt;li&gt;I tap Approve from my phone&lt;/li&gt;
&lt;li&gt;Session bridge relays the approval → Claude Code writes the file → &lt;code&gt;end_turn&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total messages exchanged over ACP: dozens. Time I spent: one tap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays local
&lt;/h2&gt;

&lt;p&gt;Everything except the model API call. Your code, sessions, config, and cost data stay on your machine. The agent calls Anthropic/Google/OpenAI for inference — that's the tradeoff — but the orchestration layer is fully yours. No relay, no third-party storing your session history.&lt;/p&gt;

&lt;p&gt;And because the bridge is protocol-based, swapping agents doesn't change anything else. Different subprocess, same messaging setup.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup wizard handles the rest. &lt;a href="https://openacp.gitbook.io/docs/platform-setup" rel="noopener noreferrer"&gt;Platform guides in the docs.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;https://github.com/Open-ACP/OpenACP&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://openacp.gitbook.io/docs" rel="noopener noreferrer"&gt;https://openacp.gitbook.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ACP registry: &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;https://agentclientprotocol.com/get-started/registry&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ACP spec: &lt;a href="https://agentclientprotocol.com/protocol/overview" rel="noopener noreferrer"&gt;https://agentclientprotocol.com/protocol/overview&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. I'm a contributor to OpenACP — happy to answer questions about the architecture or the protocol integration.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>selfhosted</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Self-hosting AI coding agents: why it matters and how to do it</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Thu, 02 Apr 2026 03:28:04 +0000</pubDate>
      <link>https://dev.to/tigergethigher/self-hosting-ai-coding-agents-why-it-matters-and-how-to-do-it-2bd7</link>
      <guid>https://dev.to/tigergethigher/self-hosting-ai-coding-agents-why-it-matters-and-how-to-do-it-2bd7</guid>
      <description>&lt;p&gt;A few months ago I audited where my code was going.&lt;/p&gt;

&lt;p&gt;Not the deployed code. The code I was writing. Every snippet I pasted into a chat window, every file I attached to get a review, every prompt I sent while debugging at 2am - all of it passed through servers I had no visibility into. I wasn't paranoid about it. I just wanted to know.&lt;/p&gt;

&lt;p&gt;The answer was: a lot of places.&lt;/p&gt;

&lt;p&gt;That started me down the path of figuring out how to keep AI in my workflow without giving up control of what I was working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "self-hosted AI" actually means in 2026
&lt;/h2&gt;

&lt;p&gt;When people say self-hosted AI, they usually mean running a local model. Ollama, LM Studio, a quantized Llama on a machine with a decent GPU. That's one approach.&lt;/p&gt;

&lt;p&gt;But for coding work, local models still can't match Claude Code or Gemini CLI. Not in context windows, not in tool use, not in how well they understand large codebases.&lt;/p&gt;

&lt;p&gt;So I stopped thinking about self-hosting the model and started thinking about self-hosting everything around it.&lt;/p&gt;

&lt;p&gt;The agent runs on my machine. It reads my files locally. The API call goes to Anthropic or Google, I accept that tradeoff. But the session state, the conversation history, the permissions, the tooling layer? None of that needs to leave my control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pieces you actually want to own
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;claude&lt;/code&gt; or &lt;code&gt;gemini&lt;/code&gt; in a terminal, the agent itself is already local. What's not local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where you interact with it (usually a terminal you have to be physically at)&lt;/li&gt;
&lt;li&gt;How sessions are managed (you open and close them manually)&lt;/li&gt;
&lt;li&gt;How you approve or deny actions when you're not at your desk&lt;/li&gt;
&lt;li&gt;Whether you can run multiple agents in parallel&lt;/li&gt;
&lt;li&gt;Cost visibility (what did this session actually cost me?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are orchestration problems, not model problems. And orchestration is exactly what can and should be self-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenACP
&lt;/h2&gt;

&lt;p&gt;I started contributing to &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;OpenACP&lt;/a&gt; because it solves this specific layer.&lt;/p&gt;

&lt;p&gt;It's a self-hosted bridge that connects AI coding agents to Telegram, Discord, or Slack. You install it on your machine, point it at your workspace, and from that point on you interact with your agents through whatever messaging app you already use.&lt;/p&gt;

&lt;p&gt;The setup is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run opens a setup wizard. You pick your platform, paste your bot token, choose a workspace, and pick a default agent. Takes about 5 minutes.&lt;/p&gt;

&lt;p&gt;After that, your Telegram (or Discord, or Slack) becomes the interface for your agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why messaging apps instead of a web UI
&lt;/h2&gt;

&lt;p&gt;I originally expected to build some kind of dashboard. But then I thought about when I actually need to interact with my agents: on my phone, on the bus, between meetings.&lt;/p&gt;

&lt;p&gt;I'm already in Telegram. My team is already in Discord. Building yet another web app to check on didn't make sense when the notification layer was already there.&lt;/p&gt;

&lt;p&gt;The practical upside is permission handling. If an agent wants to run &lt;code&gt;rm -rf&lt;/code&gt; or write to a sensitive file, it asks first. That prompt shows up as inline buttons in the chat, and I tap approve or deny from wherever I am. No need to rush back to a terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  One install, multiple agents
&lt;/h2&gt;

&lt;p&gt;OpenACP uses the &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;ACP registry&lt;/a&gt; to discover agents. Claude Code, Gemini CLI, Codex, Cursor, Cline, goose, and a bunch of others are all supported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp agents                     &lt;span class="c"&gt;# see what's available&lt;/span&gt;
openacp agents &lt;span class="nb"&gt;install &lt;/span&gt;gemini      &lt;span class="c"&gt;# install from registry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run different agents in different sessions at the same time. I usually have Claude Code running a longer task in one thread while I'm asking Gemini something quick in another. Each session gets its own topic/thread in the chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daemon side
&lt;/h2&gt;

&lt;p&gt;Running this as a background service is what makes it actually useful day-to-day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp start   &lt;span class="c"&gt;# start daemon&lt;/span&gt;
openacp stop    &lt;span class="c"&gt;# stop it&lt;/span&gt;
openacp status  &lt;span class="c"&gt;# check&lt;/span&gt;
openacp logs    &lt;span class="c"&gt;# tail logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The daemon survives terminal sessions and starts on boot. Close your laptop, open it at a coffee shop, your agents are still there. Sessions persist too, so you reconnect and pick up where you left off.&lt;/p&gt;

&lt;p&gt;If something breaks, run &lt;code&gt;openacp doctor&lt;/code&gt;. It checks your config, bot token, agent installs, and daemon status, then tells you exactly what's wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays private
&lt;/h2&gt;

&lt;p&gt;I want to be clear about what this doesn't do. The API calls to Anthropic or Google still happen. The model inference is not local.&lt;/p&gt;

&lt;p&gt;But here's what stays on your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your code and files (the agent reads them locally)&lt;/li&gt;
&lt;li&gt;Your session history and conversation state&lt;/li&gt;
&lt;li&gt;Your config and credentials&lt;/li&gt;
&lt;li&gt;Your cost data and usage logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The orchestration layer is fully yours. There's no relay service sitting between you and your agent, and no vendor storing your session history on their end.&lt;/p&gt;

&lt;p&gt;The other side of this: if I decide to switch from Claude Code to Gemini for a project, I change one config value. The messaging setup, the sessions, the permissions, the cost tracking, all of that stays the same. I'm not locked into one agent vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  A typical day with this setup
&lt;/h2&gt;

&lt;p&gt;Morning. I open Telegram, send "review the PR on branch feature/auth" to my bot. Claude Code starts reading the diff, leaves comments in the chat. I read them on the train.&lt;/p&gt;

&lt;p&gt;Afternoon. I need to scaffold a new API endpoint. I open a second session, this time with Gemini. It generates the boilerplate, I review the output in Discord while in a meeting (don't tell my manager).&lt;/p&gt;

&lt;p&gt;Something fails in CI. I forward the error log to the bot, ask it to investigate. It greps through the codebase, finds the issue, proposes a fix. I approve the file write from my phone.&lt;/p&gt;

&lt;p&gt;End of day. &lt;code&gt;openacp api status&lt;/code&gt; shows me 3 sessions ran, roughly $4.20 in API costs. Everything's logged locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wizard asks 5 things: platform, bot token, workspace path, default agent, run mode. If you already have a Telegram bot from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;, the whole thing takes under 5 minutes.&lt;/p&gt;

&lt;p&gt;For running it long-term, switch to daemon mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that it's just there. You message your bot and things happen.&lt;/p&gt;

&lt;p&gt;Detailed platform guides (Telegram, Discord, Slack) are in the &lt;a href="https://openacp.gitbook.io/docs/platform-setup" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;https://github.com/Open-ACP/OpenACP&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://openacp.gitbook.io/docs" rel="noopener noreferrer"&gt;https://openacp.gitbook.io/docs&lt;/a&gt;&lt;br&gt;
ACP registry: &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;https://agentclientprotocol.com/get-started/registry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. I'm a contributor, happy to answer questions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>How to control Claude Code from Telegram, Discord, or Slack (self-hosted, open source)</title>
      <dc:creator>psychomafia.tiger</dc:creator>
      <pubDate>Wed, 01 Apr 2026 11:22:01 +0000</pubDate>
      <link>https://dev.to/tigergethigher/how-to-control-claude-code-from-telegram-discord-or-slack-self-hosted-open-source-1jk8</link>
      <guid>https://dev.to/tigergethigher/how-to-control-claude-code-from-telegram-discord-or-slack-self-hosted-open-source-1jk8</guid>
      <description>&lt;p&gt;My workflow broke the moment I stepped away from my desk.&lt;/p&gt;

&lt;p&gt;I had Claude Code running a large refactor. Left to grab lunch. By the time I came back, it had hit a permission prompt and been sitting idle for 15 minutes, waiting for me to press "yes" on a machine I wasn't at.&lt;/p&gt;

&lt;p&gt;That's why I started contributing to &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;OpenACP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Claude Code, Gemini CLI, Codex - these tools are powerful but they assume you're sitting at your terminal. The moment you're on your phone or away from your desk, you lose visibility and control.&lt;/p&gt;

&lt;p&gt;The workarounds people try: SSH into a tmux session (fragile on mobile), set up a VPN and remote desktop (slow), or just hope the agent doesn't get stuck. None of these are actually good.&lt;/p&gt;

&lt;p&gt;What I wanted: send a message from Telegram, see the agent's tool calls streaming in real time, and approve or deny actions from my phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What OpenACP does
&lt;/h2&gt;

&lt;p&gt;OpenACP is a self-hosted bridge between your messaging platform and your AI coding agent. It uses the &lt;a href="https://agentclientprotocol.com" rel="noopener noreferrer"&gt;Agent Client Protocol (ACP)&lt;/a&gt; - an open standard for editor-agent communication - to connect agents like Claude Code, Gemini CLI, and Codex to Telegram, Discord, and Slack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You (Telegram / Discord / Slack)
  |
OpenACP (bridge + session manager)
  |
AI Agent (Claude Code, Codex, Gemini CLI, ...)
  |
Your Codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything runs on your machine. No cloud relay, no third party with access to your code, no subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started: Claude Code on Telegram
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node.js 20+ required. Claude Code needs to be installed separately.&lt;/p&gt;

&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run opens an interactive setup wizard. It asks which platform you want (Telegram, Discord, Slack, or all three), your bot token, which workspace directory to use, which agent to default to, and whether to run in foreground or daemon mode.&lt;/p&gt;

&lt;p&gt;For Telegram you need a bot token from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;. The wizard validates the token and auto-detects your chat ID - no config file editing.&lt;/p&gt;

&lt;p&gt;Once running, send any message to your bot. OpenACP creates a session, spawns Claude Code as an ACP subprocess, and streams everything back.&lt;/p&gt;

&lt;p&gt;Each session gets its own forum topic. You can run multiple agents in parallel, each in its own topic or thread depending on the platform.&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%2Fp1k83dzxga4eq7gf72z0.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%2Fp1k83dzxga4eq7gf72z0.png" alt="Control Panel showing OpenACP in Telegram with session management buttons" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a session looks like
&lt;/h2&gt;

&lt;p&gt;When the agent is working, you see the tool calls as they happen:&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%2Fxsv1dt9ggejljjgpoah2.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%2Fxsv1dt9ggejljjgpoah2.png" alt="Agent actively reading files, writing plans, dispatching tasks in real time" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;File reads, writes, grep calls, terminal commands - all streamed to your chat. If the agent needs to do something that requires approval, you get inline buttons in the message: Approve or Deny, right there on your phone.&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%2Fvtcsjenspi3u1ju1joxs.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%2Fvtcsjenspi3u1ju1joxs.png" alt="Streaming tool calls including grep, Read File, Terminal, and Task" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the thing that actually solved the lunch problem for me. I can now watch a long task from my phone and unblock it the moment it pauses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other agents
&lt;/h2&gt;

&lt;p&gt;OpenACP isn't specific to Claude Code. It works with any agent that supports ACP, which now covers most of the major ones.&lt;/p&gt;

&lt;p&gt;To use Gemini CLI from Telegram or Discord, install it the same way - same interface, same streaming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp agents &lt;span class="nb"&gt;install &lt;/span&gt;gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browse everything available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openacp agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list includes Claude Code, Gemini CLI, Codex CLI, GitHub Copilot, Cursor, Cline, goose, Qwen Code, JetBrains Junie, and more. OpenACP pulls directly from the &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;ACP registry&lt;/a&gt;, so new agents become available as soon as they register.&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%2Fydq9o1ysuyrxpct1aeqg.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%2Fydq9o1ysuyrxpct1aeqg.png" alt="Skills system showing a brainstorming skill being launched with detailed exploration output" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform support
&lt;/h2&gt;

&lt;p&gt;Telegram, Discord, and Slack are all stable. Each uses platform-native patterns: forum topics in Telegram, threads in Discord, channels and threads in Slack. You can run all three at once pointing to the same agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares to the alternatives
&lt;/h2&gt;

&lt;p&gt;Two tools people usually ask about when they see OpenACP:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;claude-code-telegram&lt;/code&gt; (the original one most people start with) is single-agent, Telegram only, no multi-session support. Fine for the basic use case.&lt;/p&gt;

&lt;p&gt;Claude Code Channels is Anthropic's official remote access feature - cloud-based, tied to Claude Code specifically. Not self-hosted.&lt;/p&gt;

&lt;p&gt;OpenACP is different in that it supports multiple agents and multiple platforms from one install, and everything stays on your machine. The tradeoff is that you have to self-host it. If that's too much friction, Claude Code Channels is the easier path.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few practical things
&lt;/h2&gt;

&lt;p&gt;Daemon mode: &lt;code&gt;openacp start&lt;/code&gt; runs it as a background service. &lt;code&gt;openacp stop&lt;/code&gt;, &lt;code&gt;openacp status&lt;/code&gt;, and &lt;code&gt;openacp logs&lt;/code&gt; handle the rest.&lt;/p&gt;

&lt;p&gt;Tunnels: if an agent needs to expose a local port (say a dev server at 3000), &lt;code&gt;openacp tunnel add 3000&lt;/code&gt; handles it via Cloudflare Tunnel, ngrok, bore, or Tailscale.&lt;/p&gt;

&lt;p&gt;Session handoff: if you're working in the terminal and want to continue on your phone, the &lt;code&gt;/openacp:handoff&lt;/code&gt; integration transfers the session without losing context. Needs to be set up first with &lt;code&gt;openacp integrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Usage tracking: optional token counting and cost reports per session. You can set a monthly budget limit that pauses the agent when hit.&lt;/p&gt;

&lt;p&gt;Doctor: &lt;code&gt;openacp doctor&lt;/code&gt; checks your daemon, bot token, agent installation, storage, and tunnel config, and tells you exactly what's broken if something isn't working.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openacp/cli
openacp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source and issues: &lt;a href="https://github.com/Open-ACP/OpenACP" rel="noopener noreferrer"&gt;https://github.com/Open-ACP/OpenACP&lt;/a&gt;&lt;br&gt;
Docs (platform setup guides): &lt;a href="https://openacp.gitbook.io/docs" rel="noopener noreferrer"&gt;https://openacp.gitbook.io/docs&lt;/a&gt;&lt;br&gt;
ACP registry (browse agents): &lt;a href="https://agentclientprotocol.com/get-started/registry" rel="noopener noreferrer"&gt;https://agentclientprotocol.com/get-started/registry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. I'm a contributor. Happy to answer questions below.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>opensource</category>
      <category>telegram</category>
    </item>
  </channel>
</rss>
