Troubleshooting
Symptoms first. Find yours, apply the fix, and if none of it helps, the last section tells you what to send us.
Start here: is the gateway actually up?
Nearly every problem below is really this one. Ask the gateway directly:
curl -s http://127.0.0.1:7376/health
A healthy gateway answers with its status, version, and object counts:
{
"status": "ok",
"service": "permaura",
"version": "1.0.0",
"agents": 2,
"capabilities": 14,
"grants": 3,
"audit_chain_intact": true
}
- No answer at all → the gateway isn't running, or something else has its port. See Port conflicts.
audit_chain_intact: false→ the audit log has been altered or truncated on disk. That's exactly what the hash chain exists to detect. Get in touch rather than carrying on.
Pairing and installation
The pairing code expired
Codes are short-lived and single-use, and the connect dialog shows a live countdown. When one lapses the dialog mints a fresh one on its own — reopen Settings → Gateway, copy the new command, and run that. An expired code is simply dead; nothing is left half-configured.
"Command not found" after the curl install
The one-line installer covers macOS and Linux. On Windows it stops and tells you to use the npm launcher instead:
npx -y permaura start --pair PGW-XXXXXXXX
That runs on macOS, Linux and Windows, Intel and Apple silicon alike, and fetches the right binary for your machine.
Port conflicts
The gateway binds 127.0.0.1:7376 by default. 7376 is "PERM" on a phone keypad, and it was picked
because nothing else conventionally claims it: it is unassigned by IANA, it sits outside the ephemeral
ranges the OS hands out to outbound connections, and it is nowhere near the ports development tooling
reaches for by habit. Conflicts should be rare. When one happens, this is how to see it.
Find out what is on the port
On macOS and Linux:
lsof -nP -iTCP:7376 -sTCP:LISTEN
On Windows:
Get-NetTCPConnection -LocalPort 7376 -State Listen | Select-Object OwningProcess
If the answer is another Permaura gateway, permaura status will say so, and permaura stop --force
ends it. The gateway diagnoses that case itself and tells you which of the two it is, so you should
rarely need the commands above for it.
The part that surprises people: a shared port still looks broken
A second program on the port does not necessarily stop the gateway binding, and that is the confusing case worth knowing about.
If the other program listens on the wildcard address (shown as *:7376 by lsof, which is what a
Node or Bun dev server does by default), the gateway can still bind 127.0.0.1:7376 beside it. Both
sockets are live at once. Which one you reach then depends on how you spell the address:
| You ask for | You reach |
|---|---|
http://127.0.0.1:7376 | the specific listener, usually the gateway |
http://localhost:7376 | whichever the OS resolves first, often the wildcard listener |
http://[::1]:7376 | the wildcard listener |
So curl 127.0.0.1:7376/health can answer correctly while an agent pointed at localhost:7376 talks
to something else entirely, and nothing reports an error. If a health check passes but a client cannot
connect, compare the two spellings:
curl -s http://127.0.0.1:7376/health
curl -s http://localhost:7376/health
Two different answers means two different servers. Free the port and restart the gateway.
Move the gateway to another port
Installing:
curl -fsSL https://permaura.com/install | sh -s -- --pair PGW-XXXXXXXX --port 7377
Running the binary or the npm launcher directly, set the address instead:
PERMAURA_ADDR=127.0.0.1:7377 npx -y permaura start
Then tell the console where to look: Settings → Gateway → Advanced, and set the browser URL to
http://127.0.0.1:7377.
If you run the gateway through the desktop app, do all of this in one place instead: Settings → Gateway → Gateway port. It moves the gateway, checks it came up on the new port, and tells your workspaces where it went. If it cannot start there it puts the old port back and says why.
If you moved the gateway some other way and the console is still calling the old address:
permaura advertise http://127.0.0.1:7377
On a VM, container or remote host
The same conflict can come from outside the machine you are looking at. A port forward claims the port
on the host, so -p 7376:7376 on a container, or hostfwd=tcp::7376-:7376 on a QEMU VM, makes the
host's 7376 unavailable to anything else, including a gateway you meant to run on the host itself.
If you run gateways in both places, give the guest its own port on both sides and tell the console which one to call. Three things have to line up, and only the last one is easy to forget:
-
Bind a port inside the guest. It must listen on all interfaces, or the forward has nothing to reach:
curl -fsSL https://permaura.com/install | sh -s -- --pair PGW-XXXXXXXX --host 0.0.0.0 --port 7377 -
Forward that port from the host. For QEMU,
hostfwd=tcp::7377-:7377; for Docker,-p 7377:7377. Pick a host port nothing else wants: the host's own gateway is on 7376. -
Advertise the address the console should call. This is the step the installer cannot work out for you, because it runs inside the guest and the address that matters is the host's:
permaura advertise http://127.0.0.1:7377
The console reaches a gateway on your own machine directly, at the address it has on file, so that
address has to be one the browser can resolve. From the host's browser, a guest behind a port
forward is 127.0.0.1:<host port>. The guest's own LAN address feels like the right answer and is
not.
PERMAURA_ADVERTISE_ADDR does the same job at install time, if you would rather not run a second
command:
PERMAURA_ADVERTISE_ADDR=127.0.0.1:7377 \
curl -fsSL https://permaura.com/install | sh -s -- --pair PGW-XXXXXXXX --host 0.0.0.0 --port 7377
A gateway that has to be reachable from another machine entirely is a different question, and port forwards are not the answer to it. Use remote access instead.
What the apps show
The desktop app names the process holding the port and stops retrying rather than looping, and the console shows Address in use with the address it tried. Neither will guess: if the program on the port is not a Permaura gateway, nothing in the app or the console can move it, and the fix is on the machine.
My gateway is on an older version than the latest
Expected if you installed with the curl one-liner: it pins a fixed release and never upgrades itself. The npm launcher does update on every run.
Check what you're on, then update in place:
curl -s http://127.0.0.1:7376/health | jq .version
npx -y permaura upgrade
That replaces the binary in ~/.permaura/bin whichever way you originally installed it. Restart the gateway afterwards.
macOS won't open the desktop app
The Permaura desktop app for macOS is signed and notarised, so a normal download and open works. If macOS blocks it, you almost certainly have a copy from somewhere other than permaura.com/download — delete it and download again from there. Don't work around a Gatekeeper warning on a build whose origin you're not sure of.
The Linux app has no tray icon and no window
The Linux app lives in the tray, and the tray needs a desktop that hosts one. Most do; stock GNOME does not until you install and enable the AppIndicator and KStatusNotifierItem extension. Without a host, the icon is real but has nowhere to appear, and because the window starts hidden there is nothing on screen at all - while the gateway underneath runs perfectly.
The app checks for a tray host at start-up and opens its window instead when there isn't one, so this should not happen on a current version. To open the window at any time:
permaura-desktop --show
Run it while the app is already running and it raises the existing window rather than starting a second copy. It is also the right command for a desktop launcher or a login item on a session with no tray.
Windows says "unknown publisher"
Expected right now. The Windows installer isn't code-signed yet, so SmartScreen shows "Windows protected your PC" with an unknown publisher. Choose More info, then Run anyway.
Only do that for a copy downloaded from permaura.com/download. If you got the installer anywhere else, delete it and download it again from there.
Two things are worth knowing. The warning is about who signed the installer, not about what it does. And once installed, the app's own updates are verified independently: every update carries a signature checked against a key baked into the app, on every platform, so the update path is protected whether or not the installer was signed. Signed installers are coming.
If you'd rather avoid the warning entirely, skip the desktop app and run the gateway from a terminal with npx -y permaura start — binaries fetched that way don't trip SmartScreen.
Gateway status in the console
It says "unverified" but everything works
Expected for a local gateway. Permaura's servers can't reach 127.0.0.1 on your machine to confirm it's alive, so they don't claim it is. Your browser is talking to it directly, and the data you're seeing is genuinely live. Nothing to fix — see Go live.
It says "offline" and no data loads
Work down this list:
curl -s http://127.0.0.1:7376/health— if that fails, the gateway isn't running. Start it again.- If it answers on a different port than the console expects, set the browser URL under Settings → Gateway → Advanced.
- If the gateway is remote or hosted, the console reaches it through Permaura's server-side proxy — check the public URL is still resolving.
- Reload the console. The status pill re-polls on load.
I'm still seeing sample data
You're in demo mode. It's a per-workspace choice you made once, and it shows a fully working console over sample data: every action responds, but nothing is written and nothing is real. Connect a gateway to switch the same pages over to your own state, as described in Go live.
Agents
The agent connected but shows as Pending
That's the design. A local agent enrolled with a one-time code always arrives Pending until you click Approve on the Agents page. A remote agent that signed in arrives approved already, unless Require manual approval for new agents is on, which holds every new agent for your OK.
The agent connects but every tool is refused
Permaura is deny-by-default: approving an agent lets it connect, not act. It can do nothing until a grant allows it. Open the audit log — the denied entry names the capability and connection that were refused, which tells you exactly what to allow.
Also worth checking:
- The capability is enabled on the connection. A disabled capability is refused for everyone, whatever your policies say.
- The connection's kill switch is off.
- The grant hasn't hit its budget or passed its expiry, and isn't paused.
The client shows no Permaura tools at all
- Restart the client. Most MCP clients read their config only at startup.
- Check the code hasn't expired. Enrolment codes are single-use and last 15 minutes. Mint a new one with New code and paste the fresh config in.
- Check the URL. A hosted client like ChatGPT cannot reach
127.0.0.1at all; it needs the public URL from remote access.
A call is stuck "awaiting approval"
It's waiting for a human on purpose. Open Control → Approvals and approve or deny it. Requests are single-use and time-boxed, so an ignored one eventually expires and the agent gets a refusal rather than hanging forever.
If the queue says a signature is required and you can't produce one, see Approvals: once a device is paired the gateway stops accepting unsigned approvals. Lowering that bar again needs the machine running the gateway if it's local or tunnelled, or a fresh sign-in plus a signature from a device you still hold if it's hosted. Playbooks has the recovery for a hosted gateway whose last device is gone.
Pairing an approval device says a step-up is required
You're on a hosted gateway, where enrolling a signing key needs a fresh sign-in and not just an open session. Prove it's you again in the app when it asks, with your password or a two-step code. A session cookie, however recent, does not count as a re-authentication.
Pairing says the ticket expired, or was already used
Both mean: start the pairing again from the beginning. The proof permaura.com issues lasts three minutes, is tied to that one key, and is spendable exactly once, so a screen left sitting or a retry after one attempt already went through needs a fresh one. Nothing is wrong with the device; sign in again and it re-issues.
The device's own proof is separate and expires in two minutes. If the app or the CLI says the proof is stale, regenerate it on the device rather than re-running the same command.
Pairing answered "waiting for a co-signature"
Expected on a hosted gateway that already trusts a device: the second and every later device is confirmed by a device already paired, from that device's own approval queue. Open the Permaura app on the phone you already have and approve the enrolment there. A console click can't do it, because the confirmation has to carry that device's signature, and the new device can't confirm itself.
If the device that would confirm it is lost, the request will never resolve. Revoke the lost device first, then pair the new one; see Playbooks.
Revoking this device says you need to confirm it's you
You're on a hosted gateway and this is your last usable approval device, which most people discover at exactly this moment. Removing it puts the gateway back to trusting the next device that pairs, on first use, so it's the one revocation that loosens rather than tightens and it takes the same fresh sign-in that pairing does. Prove it's you again with your password or a two-step code and the revocation goes through.
Nothing else asks for this. Revoking a device while another usable one remains never does, on any gateway, and neither does anything on a local or tunnelled gateway.
Expect to be asked again when you pair the replacement. That's two sign-ins for the whole recovery, and they're deliberately separate: the proof that revokes cannot enrol, and the proof that enrols cannot revoke. Playbooks has the order to do them in.
My saved change on a hosted gateway says "pending" instead of done
Once an approval device is paired to a hosted gateway, control-plane changes that aren't strictly tightening wait for a signature from that device. That covers new policies, lowering a rung, widening a grant, approving an agent, and un-pausing the broker. Approve it in the app and the change applies. Tightening changes still take effect immediately.
Remote access
The tunnel is up but agents still can't connect
This is the most common remote-access problem, and it's almost always the second step being skipped. Provisioning the tunnel is only half of it; your gateway also has to be restarted knowing about it. Until it is, it won't verify Permaura sign-ins and every remote agent is turned away.
- Desktop app — this cannot happen: the app provisions, starts the tunnel and relaunches the gateway as one step. If the switch is on and the dot beside your URL is grey rather than green, the tunnel itself is down — turn it off and on again.
- Headless — run both commands from the console's Remote access panel:
cloudflared tunnel run --token …and a gateway restart carryingPERMAURA_PUBLIC_URL,PERMAURA_OWNER_SUB,PERMAURA_AS_ISSUERandPERMAURA_AS_JWKS.
Full walkthrough in Remote access.
The Remote tab has no URL to give me
Remote access isn't on for that gateway yet, or your plan doesn't include it. Remote access is a Pro feature; on Free the switch offers an upgrade instead.
I'm bringing my own tunnel
Use an HTTP tunnel — ngrok, a Tailscale Funnel, Cloudflare — never a raw TCP forwarder like ssh -R or a bare socat pipe. The gateway tells local traffic from tunnelled traffic by the forwarding headers an HTTP tunnel adds. A raw forwarder adds none, so internet traffic would look local and reach controls that must stay loopback-only.
Sign-in
The two-step code never arrives
Check spam first. Email codes depend on your Permaura instance having email configured; if you never receive one, sign in with another factor and set up an authenticator app or a passkey instead, which need no email at all.
I've lost my second factor
Use a recovery code from the set you saved when you enrolled. If those are gone too, contact support from the email address on the account.
If you administer this for other people
Playbooks covers the admin-shaped incidents: an agent misbehaving, a leaked credential, a spend spike, a broken audit chain, and locking somebody out in a hurry.
Still stuck
Email support@permaura.com with:
- what you were doing and what happened instead,
- the output of
curl -s http://127.0.0.1:7376/health(it contains no secrets), - whether the gateway is local, tunnelled, or hosted,
- the audit log entry id for the action, if there is one.
Never send us an API key, a gateway token, or a pairing code. We never need one, and anything you paste into a support thread should be treated as burned.