El Remoto - browse any GitHub repository in Emacs just like any local - without cloning.
Wouldn’t it be nice to be able to C-x C-f and just browse any GitHub repo?
remoto.el registers a virtual filesystem via file-name-handler-alist that translates standard Emacs file operations into GitHub API calls via the ghub library. The result: find-file, dired, tab-completion, dired-subtree - all standard Emacs file tooling works against a remote GitHub repo. Read-only.
It was demoed at Emacs ATX Meetup of July 2026. Here’s the video. Link to the complete video of that meetup (~2 hrs) is in the description.
Sometimes you just want to look at code. Check a function signature, read a README, browse a project structure. Cloning a repo for that is overkill - it takes disk space, creates another directory to manage, and breaks your flow.
For now it works only with GitHub repos. Future plans include support for other forges - GitLab, Codeberg, etc.
Loading the package defines things and changes nothing. global-remoto-mode is the switch: it installs the file handler, the URL rewriting in find-file and dired, and the completion metadata, and removes them all when turned off. Nothing else is needed:
(use-package remoto
:straight (:host github :repo "agzam/remoto.el"))The mode turns itself on at first use, whichever way that comes: M-x remoto-browse, an Embark open action, or a /github: or /gh: path typed at C-x C-f, C-x d or any other file prompt. For the last one the package autoloads register a small handler for those two prefixes, the way TRAMP loads on the first remote path; it hands over to the real handler and steps aside. Each of these says so once in the echo area. To have the mode on from the start, and skip that message, put (global-remoto-mode 1) in your config.
The package is not on MELPA yet. Maybe upvote the submission PR, who knows, perhaps it gets accepted faster.
The main entry point. Accepts any GitHub URL format:
M-x remoto-browse RET https://github.com/torvalds/linux RET
M-x remoto-browse RET git@github.com:torvalds/linux.git RET
M-x remoto-browse RET torvalds/linux RETGitHub URLs are detected automatically in dired and find-file:
C-x C-f /github:torvalds/linux RET
;; shorthand also works
C-x C-f /gh:torvalds/linux RET
;; complete urls work too
C-x C-f https://github.com/torvalds/linux/blob/master/README RETThe /github: prefix (/gh: shorthand works too) enables multi-level completion directly in find-file (C-x C-f). Three delimiters after owner/repo control the mode: / for files, @ for branches/tags, # for issues/PRs.
/github: -> your user + orgs (when authenticated) /github:tor -> users/orgs matching "tor" /github:torvalds/ -> repos (with descriptions) /github:torvalds/linux/ -> files on default branch (with last commit messages) /github:torvalds/linux@ -> branches + tags (grouped) /github:torvalds/linux@master: -> file-level completion /github:torvalds/linux# -> issues + PRs
Each level shows annotations: repo descriptions, issue/PR titles and state, file commit messages, user/org type. Works with vanilla completion, Vertico, Corfu, etc. While a GitHub request is in flight, a ⟳ sits between the prompt and the path being typed, and once the path is chosen and opens, the same mark sits in the echo area; remoto-browse shows the same two, and remoto-show-fetch-indicator turns both off.
Hitting RET on /github:owner/repo opens the default branch. /github:owner/repo#42 opens an issue/PR display buffer.
The virtual filesystem uses paths of the form:
/github:OWNER/REPO@REF:/PATH
REF is optional - omitting it uses the repo’s default branch.
Opening /github:owner/repo#42 shows a dedicated buffer with the issue or PR content. For PRs: diff stats, branch info, merge status, and review summary. For both: body + comments.
Keybindings in the display buffer: b (open in browser), g (refresh).
| Command | Description |
|---|---|
global-remoto-mode | Turn the file handler, URL rewriting and completion on or off |
remoto-browse | Prompt for a repo, open dired at root |
remoto-refresh | Clear tree cache for current repo, re-fetch |
remoto-copy-url | Copy github.com URL for current file/line to kill ring |
Optional Embark integration lives in remoto-embark.el. Embark is not a hard dependency; when it is installed the integration activates automatically, with nothing to configure. With it active, embark-act offers forge-agnostic actions on remoto targets in remoto file buffers, dired, and /github: minibuffer completion (including embark-collect).
Each target type has its own keymap of actions that reuse remoto’s forge-agnostic URL layer (copy or browse the web URL, clone, blame, permalink, open an issue/PR, and so on):
| Target | Example | Keymap |
|---|---|---|
remoto-owner | /github:torvalds | remoto-embark-owner-map |
remoto-repo | /github:torvalds/linux:/ | remoto-embark-repo-map |
remoto-branch | /github:torvalds/linux@master:/ | remoto-embark-branch-map |
remoto-issue | /github:torvalds/linux#42 | remoto-embark-issue-map |
remoto-dir | /github:torvalds/linux@master:/fs | remoto-embark-dir-map |
remoto-file | /github:torvalds/linux@master:/README | remoto-embark-file-map |
Run embark-act on a target to see its actions and keys. Cloning honors remoto-clone-url-type and opens the clone’s Magit status buffer when it finishes (dired without Magit), and Embark’s built-in url target gains R to open a forge URL at point as a remoto path.
- On first access to any file in a repo, the full directory tree is fetched via the Git Trees API (one HTTP call).
- Directory listings,
file-exists-p, completions - all served from the cached tree in memory. - File contents are fetched on demand when you actually open a file.
- Content is cached by SHA, so re-opening the same file is instant.
- Authentication, SSO, private repos - all handled transparently by
ghub.
Why this isn’t a TRAMP backend?
- TRAMP fundamentally is a shell-over-transport abstraction. Every TRAMP backend assumes a remote shell on the other end, remoto.el has no remote shell.
- TRAMP’s backend API is large. You’d need to implement or stub dozens of operations, many of which assume concepts that don’t map to a REST API (process execution, file ownership, symlink resolution, timestamps).
- TRAMP’s connection management (open/close/reuse) is built around persistent sessions. The GitHub API is stateless HTTP - there’s no connection to manage.
- TRAMP’s caching layer is path-based and per-connection. remoto’s tree cache (one API call fetches the entire repo tree, then everything is served from memory) is a fundamentally different - and much more efficient - model for a read-only tree-structured API.
file-name-handler-alistIS the same mechanism TRAMP uses. Registering there directly gives you the same integration (dired, find-file, completions) without the framework overhead.
For a read-only tree browser backed by a REST API, a direct file-name-handler is the simpler, more natural fit
Git’s own protocols let you talk to a remote repo without cloning it, so why not use that, right?
- Tree listing. There’s no raw HTTP URL that gives you a directory listing. raw.githubblabla.com can’t do directory indexes. You’d have to shell out to
git ls-tree ... etc.over the ssh, which means essentially implementing a partial git client. - Branch listing and repo search - no git protocol equivalent for those - need the API
- Current approach fetches the entire tree in one API call. Doing the same over git pack protocol means negotiating a fetch, receiving packfile data and parsing it. Much heavier, much more code.
We can only imagine a world where git’s transport layer gives you a browsable filesystem interface. It doesn’t - git’s protocols are optimized for syncing object graphs, not random-access file browsing.
- Read-only. No commits, no pushes, no file modifications. You still can copy them out.
- No git operations (log, blame, diff).
- Timestamps in dired are synthetic (the tree API has no timestamps).
- Very large repos (100k+ files) use slower per-directory fetching.
- Rate limit: 5,000 requests/hour (normal browsing stays well within this).
- Per-file commit annotations make up to 20 API calls on first directory listing (cached after).
See changelog.org for a detailed list of changes and version history.
Copyright © 2026 Ag Ibragimov
GPL-3.0-or-later. See LICENSE.

