Using 1Password CLI with Magit Forge
Authored on 2025-10-24
I've been trying to move as many of my code review processes towards Emacs, relying mostly on gh CLI tool. But
as a user of magit it has always irked me that I have to hop into kitty session just to get an idea of what
issues are there, are there any linked PR, what's the CI status. Of course, I could do it in shell mode within
emacs but I have to say that never felt pleasant as Emacs is just not a good terminal emulator.
So I started looking into what I can achieve with Magit and
The Forge
The terminology of "forge" is fairly foreign to me. Nevertheless it seems to be the accepted way of describing the various source control hosts i.e. GitHub, GitLab, Codeberg, SourceHut and many many more.
Magit helpfully describes the authentication setup in their docs https://docs.magit.vc/forge/Setup-for-Githubcom.html and in essence it boils down to querying auth-source for a tuple of (host login) where host is api.github.com/api.gitlab.com/etc and the login is a special string in the form of username^forge.
Which is what I have done, but setting up a new entry, calling it api.github.com and providing a single entry
called mishok13^forge. And... I got a 401 from GitHub.
Now first thing first is validating the token which I did and it worked. I decided to simply issue a new one just to be on the safe side, dropped the cache and... no change, still 401.
This is where I decided to take a closer look at Magit Forge itself.
How Magit Forge authenticates
The codebase is clean and easy to follow, so I thought it would be a simple case of search and yet this is where I hit my first hurdle. auth-source is mentioned only in docs of Forge and not in the code. After getting a little bit panicked, turning on emacs-debug and reading tracebacks a little bit more carefully (i.e. reading them for the first time) I have discovered that the authentication logic lives in a separate package, ghub.el. I'm yet to understand why it's not part of forge itself but I would speculate it's historical reasons. The implementation for username rendition can be found here in all its glory https://github.com/magit/ghub/blob/b12183279be880dafc6d971aa4bc1ccc39350c4a/lisp/ghub.el#L781-L782
Now a proper solution to this would be to simply defadvice that function to drop the unnecessary appendage of ^forge but that is not what I have done because I felt like fixing Just My Problem is not The Solution. After all, the problem as I saw it was that 1Password should not silently fail whenever the perfectly valid request is performed.
1password CLI
This is where I have decided to take a closer look at the library I was using for 1Password integration. What I have discovered that it's a very thin wrapper over "op", the 1Password CLI. It has little in the way of error handling which is fine in this specific case, but gobbling up the errors is not fine and should probably have been noted.
I've decided to ignore the library for the time being and simply try to run
op read "op://Private/api.github.com/mishok13_forge"
Aaaand that failed spectacularly
❯ op read "op://Personal/api.github.com/mishok13^forge"
[ERROR] 2025/11/16 20:42:04 could not read secret 'op://Personal/api.github.com/mishok13^forge': invalid secret reference 'op://Personal/api.github.com/mishok13^forge': invalid character in secret reference: '^'
Because of course it is. The correct reference looks like this "op://Private/api.github.com/c3emmozqfyosisc6mwzwotz33a" and is obviously not something
Updating 1Password-auth-source
In closing
After hopping through the hoops I've gotten to the point where forge works and yet I find myself almost never using it. It's a combination of habit and the limited capabilities of forge.