Skip to content

Remote capture fails for all documented selection paths on a headless Herdr server #23

Description

@vekexasia

Summary

On a headless remote Herdr server, none of the selection paths currently documented by Herdr Annotate delivers text to annotate.capture:

  1. Herdr mouse selection followed by prefix+a
  2. Herdr Copy Mode selection followed by prefix+a
  3. Neovim Visual selection followed by the documented <leader>a mapping

The action runs, but HERDR_PLUGIN_CONTEXT_JSON has no selected_text. Annotate then falls back to reading the server's graphical clipboard, which is unavailable on a headless host, and exits with:

No supported clipboard reader is available

This makes the documented remote setup unusable for capturing new annotations, even though the manager and other plugin actions work.

Environment

  • Herdr: 0.8.2, stable
  • Herdr Annotate: 0.3.0, lite, resolved commit fb93a1318f960792452cef6cde72a2c4f4591241
  • Server: Ubuntu 24.04 LTS, headless (DISPLAY and WAYLAND_DISPLAY unset)
  • Client: Linux, Ghostty, attached with herdr --remote devbox-hz --remote-keybindings server
  • xclip is installed on the server, but there is no graphical display/clipboard to read

Remote server configuration:

[ui]
copy_on_select = false

[keys]
prefix = "ctrl+b"

[[keys.command]]
key = "prefix+a"
type = "plugin_action"
command = "annotate.capture"
description = "annotate text"

This matches the documented remote setup.

Reproduction

1. Mouse selection

  1. Attach using server keybindings.
  2. Drag-select text in a pane and release the mouse button.
  3. Confirm the selection remains highlighted because copy_on_select = false.
  4. Press Ctrl+B, release it, then press a.

The selection disappears and Annotate reports No supported clipboard reader is available.

2. Herdr Copy Mode

  1. Press Ctrl+B, release it, then press [.
  2. Press v, move with $ so that text is visibly selected.
  3. While the selection is still visible, press Ctrl+B, release it, then press a.

The selection disappears before annotate.capture receives its context. The action again falls back to the unavailable server clipboard.

Using y first does not help: it exits Copy Mode and sends the copied text to the viewing client, while the plugin still executes and reads the clipboard on the remote server.

3. Neovim Visual mode

Install the documented mapping:

vim.keymap.set("x", "<leader>a", function()
  vim.cmd('normal! "+y')
  vim.fn.jobstart({
    "herdr",
    "plugin",
    "action",
    "invoke",
    "annotate.capture",
  })
end, { desc = "Annotate in Herdr" })
  1. Select text in Neovim Visual mode.
  2. Press <leader>a.

The OSC 52 yank reaches the viewing client's clipboard, but the plugin action runs on the remote server. The CLI invocation does not include the Visual selection as selected_text, so Annotate falls back to the unavailable remote clipboard and fails.

Actual result

Recent plugin logs for the failed captures contain:

{
  "action_id": "capture",
  "status": "failed",
  "exit_code": 1,
  "stderr": "No supported clipboard reader is available\n"
}

annotate.manage succeeds in the same installation, confirming that the plugin runtime and keybindings are otherwise working.

Expected result

At least one documented remote selection path should deliver the selected text to annotate.capture without requiring a graphical clipboard on the server.

If remote capture is not currently supported on a headless server, the Remote sessions and Neovim integration sections should say so instead of stating that prefix+a and the Visual-mode mapping work remotely.

Related upstream behavior

These Herdr reports cover parts of the failure but not the complete user-facing problem:

  • herdrdev/herdr#3353: mouse selection is absent from plugin context after mouse-up with the default copy_on_select = true. The documented false setting avoids that exact mouse-up path, but the following prefix input still clears the retained selection.
  • herdrdev/herdr#2011: direct plugin keybindings lose selected_text before context construction. It was closed administratively without a fix.

For Copy Mode, Herdr currently cancels Copy Mode before invoking a prefix custom command, which clears the selection before the plugin context is built:

For Neovim, Herdr's socket API can represent explicit context.selected_text, but herdr plugin action invoke and the documented mapping do not expose that text channel. Annotate consequently uses its server-side clipboard fallback.

I am filing this here because it is the extension's documented end-to-end workflow. The underlying fixes may require Herdr changes, an Annotate-side helper/integration that passes explicit text, or documentation changes.

Activity

  1. backnotprop commented on Aug 29, 2026

    @backnotprop
    Contributor

    Thanks, this is precise and it reproduces from source. Paths 1 and 2 are Herdr bugs, not remote-specific: prepare_terminal_key_forward clears any retained selection on every keypress (src/app/input/terminal.rs:73) before it checks for the prefix key (:123), and prefix-bound plugin actions run cancel_copy_mode_if_active() before the context is built (src/app/input/navigate.rs:86-87). Locally the plugin masks it by reading the clipboard; a headless server has none. Filed upstream with the exact lines: herdrdev/herdr#3380.

    Path 3 works on headless servers as of the next commit on main: the Neovim mapping now hands the selection over in a file ($XDG_RUNTIME_DIR/herdr-annotate-<uid>/selection) and annotate.capture takes it before trying the clipboard. Updated mapping is in the README's Neovim section; reinstall the plugin to pick it up. Leaving this open until the Herdr side lands for paths 1 and 2.

  2. backnotprop commented on Aug 29, 2026

    @backnotprop
    Contributor

    Update: there is a way to make mouse selection work on the headless server today. Keep copy_on_select = false, then trigger the action from your laptop instead of a Herdr keypress, while the selection is still highlighted:

    ssh devbox-hz herdr plugin action invoke annotate.capture

    Bind that to a key in Ghostty or your OS. The API path builds the context from the focused pane's retained selection and never goes through the keyboard handler that clears it. Verified on the same selection: this delivers selected_text; prefix+a delivers nothing (evidence posted on herdrdev/herdr#3380). README's Remote sessions section now documents this. Copy-mode selections should behave the same way (they are also retained until a keypress), but I have not exercised that path.

  3. s-KaiNet commented on Sep 24, 2026

    @s-KaiNet

    @backnotprop it looks like the workaround doesn't work any more with herdr 0.9.1+

    I connect from host (win 11) to headless ubuntu with herdr 0.9.1 using herdr --remote.
    I created a simple plugin:

    id = "context-test"
    name = "Context Test"
    version = "0.0.1"
    min_herdr_version = "0.7.0"
    platforms = ["linux"]
    
    [[actions]]
    id = "show"
    title = "Show invocation context"
    contexts = ["pane"]
    command = ["bash", "-lc", "printf '%s\n' \"$HERDR_PLUGIN_CONTEXT_JSON\""]
    

    Then I opened herdr session with --remote and made sure some text is selected (copy_on_select = false).
    In a separate windows I have normal ssh session on ubuntu, and I run

    herdr plugin action invoke context-test.show | jq '.result.context'

    or

    herdr plugin action invoke annotate.capture | jq '.result.context'

    the output is somewhat like

    {
      "correlation_id": "cli:plugin",
      "focused_pane_cwd": "/home/name",
      "focused_pane_id": "w4:p1",
      "focused_pane_status": "unknown",
      "invocation_source": "cli",
      "tab_id": "w4:t1",
      "tab_label": "1",
      "workspace_cwd": "/home/name",
      "workspace_id": "w4",
      "workspace_label": "~"
    }
    

    and the selected_text is completly missing.
    Seems for 0.9.1+ the selected_text handling completely changed ? But we need some verification from others.

  4. backnotprop commented on Sep 24, 2026

    @backnotprop
    Contributor

    Confirmed, thanks for the clean repro. Herdr 0.9.1 stopped passing the selection to CLI-invoked actions (0.8.2 did). Filed upstream: herdrdev/herdr#4599. Until it's fixed there's no working remote capture path on 0.9.1; keeping the server on 0.8.2 keeps the workaround working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions