Getting Claude Code to Open Files in Emacs Properly
Intro
I’ve been using Claude Code more and more in my workflow recently. One of the things it can do is open files in your editor - handy when you’re deep in a terminal session and want to quickly jump to a file. I asked it to open my latest blog post in Emacs and it ran:
emacsclient -n /path/to/file.md
It reported success. Nothing happened. No frame appeared, no file opened - at least not visibly. The command had technically worked, but the file was sitting in an invisible buffer on an invisible frame. Helpful.
Asking it to try again with emacsclient -c -n solved it immediately. A new GUI frame appeared with the file ready to edit. But this got me thinking - why did the first attempt fail, and how do I make sure it works every time?
The Problem: The Daemon’s Invisible Frame
If you run Emacs as a daemon (and you should), there’s a subtlety that catches people out. When the daemon starts, it creates a single frame on something called the initial_terminal. This is a non-graphical pseudo-terminal - it has no display, no window system, and is not visible to anyone. It exists purely as an internal placeholder.
When you run emacsclient -n file, emacsclient tells the server to open the file in an existing frame. The server looks at its frame list, finds that single initial_terminal frame, and opens the buffer there. The command succeeds - the buffer is loaded, the file is visited - but the frame it lands on is invisible. The file disappears into the void.
When you run emacsclient -c -n file, the -c flag tells emacsclient to create a new frame. Because your DISPLAY environment variable is set, this new frame is created as an X11 graphical frame. It’s real, visible, and the file opens in it. That’s why it works.
In short: -n alone reuses an invisible frame. -c -n creates a visible one. This is documented behaviour for emacs --daemon, but it’s the kind of thing you discover the hard way.1
The Fix
To solves the problem permanently. Claude Code supports a user-level instructions file at ~/.claude/CLAUDE.md. Anything you put in there is loaded into every session, regardless of which project or directory you’re working in. Think of it as standing instructions that follow you around.
Create or edit ~/.claude/CLAUDE.md and add:
## Editor Preferences
- When opening files in Emacs, always use `emacsclient -c` (create new frame), never `emacsclient -n` alone
- Emacs runs as a daemon so there is no visible frame to reuse without `-c`
That’s it. Next time you ask Claude Code to open a file in Emacs, it knows. No more invisible buffers, no more “try again with -c”. The instruction persists across sessions and across projects.2
Claude Code actually supports a whole hierarchy of instruction files:
~/.claude/CLAUDE.md- User-level, applies everywhere (this is the one you want)./CLAUDE.md- Project-level, shared with your team via git./CLAUDE.local.md- Project-level but personal, auto-gitignored.claude/rules/*.md- Modular topic-specific rules
More specific files take precedence over general ones, so you can override your global preferences per-project if needed. For something like editor preferences though, the user-level file is the right place.
Why This Matters for AI Tools
This is a small thing, but it highlights something worth thinking about. AI coding assistants like Claude Code are increasingly capable of interacting with your local environment - opening files, running commands, managing processes. But they don’t always know the quirks of your specific setup. Claude Code ran a perfectly reasonable command that would work if you had an existing visible Emacs frame. It just didn’t account for the daemon’s invisible initial frame.
The CLAUDE.md approach is what makes this interesting. Rather than hoping the tool figures out your environment, you tell it once and it remembers. It’s the same principle as a well-documented Emacs config - invest a few minutes now, save yourself frustration forever.
As these tools mature, I expect this kind of integration will get smoother. For now, it’s worth knowing the underlying mechanics so you can nudge things in the right direction when they don’t quite work.
Final Thoughts
Running Emacs as a daemon is one of the best workflow improvements you can make. But the invisible initial_terminal frame is a gotcha that trips up humans and AI assistants alike. The fix is simple: always use -c to create a new frame when connecting to the daemon.
If you’re using Claude Code with Emacs, now you know why that first emacsclient -n call silently succeeds without showing anything. And if you’ve set up your aliases properly, you were probably already protected from this - just not when an external tool bypasses your shell.
Footnotes
-
You can verify this yourself by running
emacsclient -e '(frame-list)'to see the daemon’s frames, andemacsclient -e '(display-graphic-p)'to check if the current frame is graphical. On a fresh daemon, you’ll see a single frame with no graphical display. ↩ -
Once you have a
CLAUDE.mdit’s worth adding other guardrails too. I also added a rule to never push to git without confirming with me first - the kind of thing you only need to learn once the hard way. ↩