WRITING
I got tired of logging out of Claude twelve times a day, so I built Lanes
8 min read
I have a work account and a personal account. Same as most people who use these tools for a living.
Claude Desktop lets me be signed into exactly one of them.
So the day looks like this. Work Claude is open. I want to ask personal Claude something: a side project, a thing I'm reading, something I don't want in a work workspace. Sign out. Sign in. Ask the question. Sign out. Sign back into work.
I did this for about two weeks before deciding it was unacceptable, which in hindsight is two weeks longer than it should have taken.

What already existed, and why none of it fit
This is a known problem, and there are answers. They just weren't the answer I wanted.
The shell-alias approach works: point the CLI at a different config directory per account and you get clean separation. It's a good trick. It's also terminal-only and Claude-Code-only, and my problem was the desktop app.
Then there are the token switchers, small open-source CLIs that swap OAuth credentials in and out of a single credential store. They're clever, and they work. But read that sentence again: a tool that moves my auth tokens around. Maybe I trust it. Maybe I read every line. It's still a category of tool I'd rather not have running on my machine.
Nobody had shipped the obvious thing: a visual launcher that doesn't go anywhere near a credential.
So that's the gap. It's a small one. It's also exactly the size of a weekend.
The trick is one flag
Claude Desktop is an Electron app, and Electron honors --user-data-dir. Launch the binary with that flag and it reads and writes all of its state, including your signed-in session, to the directory you name.
The part that makes it actually work: the single-instance lock lives inside that directory. Two launches pointed at two different data dirs each get their own lock, which means they don't see each other, which means they both run.
One profile is one directory. That's the whole mechanism.
/Applications/Claude.app/Contents/MacOS/Claude \
--user-data-dir="$HOME/Library/Application Support/Lanes/profiles/claude-work"
Run that twice with two different paths and you have two Claudes, signed into two accounts, sitting next to each other. That's not a description of Lanes. That's the entire thing Lanes does, and you can run it right now without installing anything of mine.
One thing that cost me longer than it should have: launch the binary directly. Don't use macOS open. open routes through LaunchServices, which sees a running instance and focuses it instead of starting a new one, so you sit there wondering why your second profile keeps opening the first one's window. Spawn the executable and the problem disappears.
What actually worked, and what didn't
I wanted this for three apps. I got it for one. That's worth writing down, because "it's just a flag" turns out to be true for exactly as many apps as it's true for.
Claude Desktop: works. Launched twice with two different --user-data-dir paths, both directories populated independently. Cookies, local storage, session storage, all separate. Two instances running at once, two accounts, sessions that survive a relaunch. This is the supported path and it's the only entry in the app table.
Codex: doesn't. This one's the interesting failure, because it looks like it should work. Codex is Electron. It accepts --user-data-dir without complaint. It then ignores it completely: lsof shows it writing to the default ~/Library/Application Support/Codex regardless of what you passed, and both target directories stay empty. The app overrides its own userData path, so the flag is a no-op. The second launch then dies on EEXIST against a shared IPC socket at a fixed temp path (…/T/codex-ipc/ipc-501.sock), which is the real tell: the isolation boundary isn't the data dir at all. Two instances, one session, no way in from the outside.
ChatGPT: it isn't even the same kind of app. I'd assumed it was Electron like the others. It isn't. Pull the bundle apart and it's a native Swift app: SwiftUI, AppKit, Combine, the Swift runtime, an asset catalog. No Electron framework, no app.asar, no helper processes. There is nothing in there for --user-data-dir to bite on, because the flag is an Electron flag and this is not an Electron app. In the one launch I tried, the data dir I passed stayed empty, which is what you'd expect from a flag the binary has never heard of.
I'll be precise about the limit of that: I confirmed what it's built with, and I confirmed the directory stayed empty. I did not get as far as running it twice and watching the second launch fail. So call it a strong finding with an untested last mile. If someone wants to prove me wrong, the repo takes pull requests.
Where an app didn't cooperate, the move was to mark it unsupported rather than reach for a workaround. Every workaround I could think of involved touching something I'd decided not to touch. Which brings me to the part of this I care about most.
The line I decided not to cross
Lanes never reads, copies, or moves an auth token. Not once. It creates an empty directory and spawns a process with a flag. The only thing it writes is a lanes.json of profile metadata: an id, a label, a color, a path. That's the entire surface area.
The one honest footnote: if you delete a profile and explicitly confirm "also delete the data folder," it will remove_dir_all that lane's directory, session and all. That's a delete you asked for, guarded so it can only ever touch a folder Lanes created. It cannot delete your default Claude session even if you tell it to.
It makes zero network calls. No telemetry, no analytics, no update check, no phone-home. There's no updater configured, no HTTP client in my code, and the webview CSP is default-src 'self' with no remote fonts, no CDN, nothing. I checked lsof -nP -i on the running app and it holds zero network sockets.
Full disclosure, because it's the kind of thing someone will find and I'd rather they find it here: reqwest and hyper are compiled into the binary as unused transitive dependencies of the framework. They're linked and never invoked. I can't easily remove them; I can tell you they're there and that nothing calls them.
That constraint wasn't a nice-to-have I added at the end. It was the design brief, and it's the reason the tool is boring in the best possible way. There's nothing in it that could betray you, because there's nothing in it that touches anything sensitive.
I'll be honest that this is also a stance. Plenty of tools in this space do touch tokens and are probably fine. I just think that when you're building a utility that sits between a developer and their credentials, "probably fine" isn't the bar. And the fact that the careful version was also the simpler version tells you something about how often the shortcut was necessary in the first place.
The bug that ate an afternoon
The launcher was easy. The part that broke was the little green dot.
Lanes shows you which lanes are currently running. To do that it scans process command lines, pulls out the --user-data-dir value, and matches it against known profile paths. Simple. It also never worked, and it failed silently, which is the worst way for anything to fail.
The extraction did this:
rest.split_whitespace().next()
Grab everything after the flag, split on whitespace, take the first token. Reasonable, except the value is:
/Users/me/Library/Application Support/Lanes/profiles/claude-work
Application Support. There's a space in it. So the "path" I extracted was /Users/me/Library/Application, which matched nothing, ever, so the indicator never lit.
The part that stings is the comment I'd written right above it, reassuring myself that this was safe because we never put spaces in profile ids. Which was true. It was also completely beside the point, because the space wasn't in the id. It was in Apple's directory name, sitting in the middle of every single path, and had been for about thirty years.
The fix was to stop tokenizing and substring-match the whole command line against known data dirs. Four lines. The lesson is older than the bug: the assumption you write down to justify a shortcut is exactly the assumption worth checking, and the thing that will get you is never the part you were worried about.
What it is
A menu-bar item. Click it, get a dropdown, click a row, that app opens with that profile. Add, rename, remove profiles. It'll also adopt your existing Claude session as a lane, so you don't have to sign back in to the account you already had. No Dock icon, no main window, no preferences empire. The dropdown is the whole interface.

The isolation mechanism is about ten lines of Rust. The full path from click to running app, including resolving the binary and hiding the panel, is about forty. Everything else in the repo is profile CRUD, the tray, and the panel UI.
It took a weekend. It is finished, in the sense that I'm not adding anything to it.
Take it
Lanes is free and open source: github.com/synthetixis/lanes
Two honest caveats, which are also in the README. It's unofficial and not affiliated with Anthropic, and it's a personal-productivity utility rather than anything sanctioned. And check your plan's terms before running multiple accounts, particularly if one of them is a Team or Enterprise account. That's your call to make, not mine.
If a vendor update breaks the launch path someday, that's the deal you sign up for with a tool this thin. The mechanism is about ten lines and it's all in the open, so fork it, fix it, send it back.
Built by Synthetixis, where we build AI-native products and partner with a few teams to build theirs. More on why we think most AI software is built backwards.