Skip to main content

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 forYou reach
http://127.0.0.1:7376the specific listener, usually the gateway
http://localhost:7376whichever the OS resolves first, often the wildcard listener
http://[::1]:7376the 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:

  1. 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
  2. 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.

  3. 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:

  1. curl -s http://127.0.0.1:7376/health — if that fails, the gateway isn't running. Start it again.
  2. If it answers on a different port than the console expects, set the browser URL under Settings → Gateway → Advanced.
  3. If the gateway is remote or hosted, the console reaches it through Permaura's server-side proxy — check the public URL is still resolving.
  4. 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.1 at 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 carrying PERMAURA_PUBLIC_URL, PERMAURA_OWNER_SUB, PERMAURA_AS_ISSUER and PERMAURA_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.