herdr-bridge
A small VS Code extension that bridges herdr panes into VS Code. Fire a vscode:// URI from a herdr keybinding or script, and the extension opens an integrated terminal attached to that herdr pane's terminal stream.
How it works
herdr keybinding ──▶ open "vscode://file/<pane cwd>" (1)
──▶ open "vscode://clement-micol.herdr-bridge/attach?pane=<id>" (2)
│
▼
URI handler (extension, in the window focused by (1))
│ pane.get over ~/.config/herdr/herdr.sock
▼
createTerminal(herdr terminal attach <terminal_id>)
Window routing is deliberately outside the extension. URL (1) is consumed by
VS Code's main process, which opens-or-focuses the window for that folder
(native dedupe, explicit focus) and never delivers it to extensions. URL (2) is
then routed to the most recently focused window — the one (1) just focused —
where this extension attaches the terminal. Extension URIs cannot target a
window and window-openable URLs cannot reach extensions, so the pair is the
whole design. Add &windowId=_blank to (1) to force a fresh window.
The attach URI carries only the pane id; everything else (terminal id, cwd,
agent state) is fetched live from the herdr server's unix-socket API, so the
link stays correct even if it's opened late or twice.
URI contract
vscode://clement-micol.herdr-bridge/attach?pane=<pane id>
pane — a herdr pane id, for example wS:p1. Required, and the only parameter.
A raw : is accepted. URL-encoding it (wS%3Ap1) also works.
Example trigger from a herdr-side script:
open "vscode://file/${HERDR_ACTIVE_PANE_CWD}"
open "vscode://clement-micol.herdr-bridge/attach?pane=$HERDR_ACTIVE_PANE_ID"
Requirements
- The
herdr binary on PATH (used to run herdr terminal attach inside the created terminal).
- A running herdr server (socket at
~/.config/herdr/herdr.sock, protocol 17).
Install
Install from the VS Code Marketplace. This is the normal path.
code --install-extension clement-micol.herdr-bridge
You can also search for herdr-bridge in the Extensions view, or open the
Marketplace page.
This is a community extension. It is not affiliated with the herdr project.
If you installed an earlier build, remove it. VS Code treats each old id as a
separate extension and keeps them all installed.
code --uninstall-extension afresh.herdr-bridge
code --uninstall-extension herdr.vscode-bridge
Install a build instead
To run an unreleased change, build the .vsix yourself.
npm install
npm run package # produces herdr-bridge-<version>.vsix
code --install-extension herdr-bridge-*.vsix
Every CI run also attaches a .vsix artifact, and every release attaches one to
its GitHub release.
Set up the herdr shortcut
Add a shell keybinding to ~/.config/herdr/config.toml. This is the working
setup, bound to prefix+e:
[keys]
# prefix+e defaults to "open pane scrollback in $EDITOR". Move that out of the
# way first, or the binding below is rejected as a duplicate.
edit_scrollback = "prefix+shift+e"
[[keys.command]]
key = "prefix+e"
type = "shell"
command = "open \"vscode://file/$HERDR_ACTIVE_PANE_CWD\"; open \"vscode://clement-micol.herdr-bridge/attach?pane=$HERDR_ACTIVE_PANE_ID\""
description = "open editor at pane cwd"
Both open calls are needed, in this order. The first focuses the right window,
the second attaches the terminal. See How it works above.
herdr exports these variables into a type = "shell" keybinding:
| Variable |
Value |
HERDR_ACTIVE_PANE_CWD |
working directory of the focused pane |
HERDR_ACTIVE_PANE_ID |
pane id of the focused pane, for example wS:p1 |
HERDR_ACTIVE_TAB_ID |
tab id of the focused pane |
HERDR_ACTIVE_WORKSPACE_ID |
workspace id of the focused pane |
Note that HERDR_PANE_ID is a different variable. It is set inside a pane's own
shell, not in a keybinding, and it is empty here.
Then check the file and load it into the running server:
herdr config check
herdr server reload-config
Use it
- Focus the herdr pane that you want to open.
- Press
prefix+e.
- Approve the two first-use prompts (see below).
VS Code opens or focuses the window for that pane's directory. A terminal named
herdr: <pane id> appears in that window, attached to the pane's terminal
stream. The pane keeps running in herdr — this is a second view of the same
terminal, not a copy.
Two one-time prompts appear on first use. Both offer an "always allow" option:
- VS Code asks to confirm the
vscode://file/ URL, because an external app sent it.
- VS Code asks to allow the attach URI to reach this extension.
To open a fresh window instead of reusing one, add &windowId=_blank to the
vscode://file/ URL.
If nothing happens
| Symptom |
Cause |
| No window opens |
The vscode://file/ URL was blocked at the confirmation prompt. |
| Window opens, no terminal |
The attach URI was blocked, or the extension is not installed. |
Error herdr socket error |
The herdr server is not running. |
Error failed to attach to pane |
The pane id is stale. Press prefix+e again from the pane. |
Protocol notes (for future work)
The herdr socket speaks newline-delimited JSON. Facts learned empirically, not all obvious from the schema (herdr api schema --json):
- Requests are
{"id": "<string>", "method": ..., "params": {...}} — id must be a string, params is required even when empty.
- The server is one-request-per-connection: it closes the socket after each response.
HerdrClient therefore opens a fresh connection per request.
- Undispatchable requests (unknown method, malformed JSON) come back with
"id": "" rather than echoing the request id.
events.subscribe is the exception to one-shot: that connection stays open and streams {"event": ..., "data": ...} messages (no id). Not used by the extension yet.
Develop
npm run watch # tsc in watch mode
npm run lint
npm test # vscode-test
To debug, open this folder in VS Code and press F5. That launches an Extension
Development Host with the extension loaded from source.
Release
Two GitHub Actions workflows live in .github/workflows/.
| Workflow |
Trigger |
What it does |
ci.yml |
push to main, pull request |
lint, compile, test, package, upload the .vsix as an artifact |
release.yml |
push of a v*.*.* tag |
the same checks, then publish to the Marketplace and create a GitHub release |
One-time setup
- Create a publisher named
clement-micol at the Marketplace management page.
- Create an Azure DevOps personal access token with the scope Marketplace → Manage.
- Add the token as the repository secret
VSCE_PAT, with gh secret set VSCE_PAT.
Use the same Microsoft account for the Azure DevOps organization and for the
publisher. Two different accounts produce a confusing "you do not have
permission to publish" error.
The token problem, as of August 2026
The VS Code documentation tells you to set the token organization to All
accessible organizations. That is a global PAT, and Azure DevOps blocked
creation of new ones on 2026-03-15. Existing ones stop working on 2026-12-01.
So create an organization-scoped token instead. Whether the Marketplace
accepts one for publishing is an open question — see
microsoft/vscode#322741.
Test it with a real publish before you depend on it.
If it does not work, publish by hand until Microsoft resolves the issue:
npx @vscode/vsce login clement-micol
npm run package
npx @vscode/vsce publish --packagePath herdr-bridge-*.vsix
The documented replacement is Microsoft Entra ID with workload identity
federation. It costs nothing, but the published flow assumes Azure Pipelines and
an Azure DevOps service connection. It does not map onto GitHub Actions today.
Cut a release
npm version patch # or minor / major — updates package.json
git push origin main --follow-tags
The tag must match the version in package.json. The workflow checks this and
fails if the two differ. Update CHANGELOG.md before you tag.
To rehearse without publishing, run the Release workflow by hand with
dry_run enabled. It builds and attaches the .vsix but skips the publish
step.
Source layout
src/herdrClient.ts — minimal client for the herdr socket API (one connection per request, promise-based, safe when herdr is down).
src/extension.ts — the onUri activation and the attach flow; all VS Code-facing logic.