Pre-submission checklist
What happened?
Paperclip Desktop remote connection preflight reports that a remote Paperclip server does not expose the health endpoint / is not a Paperclip host when connecting to an authenticated public Paperclip server whose /api/health response omits authReady.
The health endpoint is publicly reachable and returns HTTP 200 JSON, but the Desktop preflight parser rejects the response because it contains deploymentExposure: "public" while omitting authReady.
This creates a confusing user-facing error: the endpoint is reachable, but Desktop says the health endpoint is not accessible or the host does not appear to expose it.
Original failing health response
Before the workaround, the public health endpoint was reachable and returned HTTP 200:
curl -i https://paperclip.example.com/api/health
Response body:
{
"status": "ok",
"deploymentMode": "authenticated",
"deploymentExposure": "public",
"bootstrapStatus": "ready",
"bootstrapInviteActive": false
}
Important detail: authReady is missing.
Session probe response
The auth/session probe returns the expected signed-out response:
curl -i https://paperclip.example.com/api/auth/get-session
Response:
HTTP/2 401
content-type: application/json; charset=utf-8
{"error":"Board authentication required"}
This appears to be expected and is handled by Desktop as signed_out when the health response passes validation.
Expected behavior
Desktop should accept this host as a valid authenticated Paperclip remote, or the server should include authReady in its public authenticated health response.
A valid authenticated/public health response should look like:
{
"status": "ok",
"deploymentMode": "authenticated",
"deploymentExposure": "public",
"authReady": true,
"bootstrapStatus": "ready",
"bootstrapInviteActive": false
}
Steps to reproduce
Follow the VPS install guide in the docs
Paperclip version or commit
2026.609.0
Deployment mode
Self-hosted server
Installation method
npm / pnpm global install
Agent adapter(s) involved
Database mode
None
Access context
None
Node.js version
No response
Operating system
No response
Relevant logs or output
Relevant config (if applicable)
Additional context
Actual behavior
Paperclip Desktop rejects the remote during preflight with a message equivalent to:
- This host does not appear to expose the Paperclip health endpoint.
- The health endpoint is not publicly accessible.
However, /api/health is publicly accessible and returns HTTP 200 JSON.
Root cause observed
In Paperclip Desktop src/connection/preflight.ts, the health parser accepts either:
- Full health shape:
deploymentExposure !== null
authReady !== null
or:
- Redacted authenticated shape:
status === "ok"
deploymentMode === "authenticated"
bootstrapStatus !== null
bootstrapInviteActive !== null
deploymentExposure === null
authReady === null
The server response is neither shape:
deploymentExposure is present: "public"
authReady is absent/null
So parseHealthPayload() returns null, and Desktop reports not_paperclip / health endpoint unavailable even though the endpoint is reachable.
Pseudo-simulation of the failing shape:
const body = {
status: "ok",
deploymentMode: "authenticated",
deploymentExposure: "public",
bootstrapStatus: "ready",
bootstrapInviteActive: false,
};
const authReady = typeof body.authReady === "boolean" ? body.authReady : null;
const deploymentExposure = body.deploymentExposure === "private" || body.deploymentExposure === "public"
? body.deploymentExposure
: null;
const hasFullHealthShape = deploymentExposure !== null && authReady !== null; // false
const hasRedactedAuthenticatedShape =
body.status === "ok" &&
body.deploymentMode === "authenticated" &&
body.bootstrapStatus !== null &&
body.bootstrapInviteActive !== null &&
deploymentExposure === null &&
authReady === null; // false because deploymentExposure is present
Relevant config (sanitized)
/home/paperclip/paperclip.env:
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
PAPERCLIP_ALLOWED_HOSTNAMES=paperclip.example.com
DATABASE_URL=postgresql://paperclip:REDACTED@127.0.0.1:5432/paperclip
Relevant parts of /home/paperclip/.paperclip/instances/default/config.json:
{
"database": {
"mode": "postgres",
"connectionString": "REDACTED"
},
"server": {
"deploymentMode": "authenticated",
"exposure": "public",
"bind": "loopback",
"host": "127.0.0.1",
"port": 3100,
"allowedHostnames": [
"paperclip.example.com"
],
"serveUi": true
},
"auth": {
"baseUrlMode": "explicit",
"disableSignUp": false,
"publicBaseUrl": "https://paperclip.example.com"
},
"storage": {
"provider": "local_disk"
}
}
Relevant Nginx reverse proxy shape before workaround:
location / {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $paperclip_forwarded_proto;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
Workaround used successfully
I applied a narrow Nginx workaround for only /api/health to inject "authReady": true into the JSON response.
Nginx location block:
location = /api/health {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $paperclip_forwarded_proto;
proxy_set_header Accept-Encoding "";
sub_filter_types application/json;
sub_filter_once on;
sub_filter '"deploymentExposure":"public","bootstrapStatus"' '"deploymentExposure":"public","authReady":true,"bootstrapStatus"';
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
After reloading Nginx, the public health endpoint returns:
{
"status": "ok",
"deploymentMode": "authenticated",
"deploymentExposure": "public",
"authReady": true,
"bootstrapStatus": "ready",
"bootstrapInviteActive": false
}
Desktop then accepts the remote.
Verification after workaround
curl -s https://paperclip.example.com/api/health
returns:
{"status":"ok","deploymentMode":"authenticated","deploymentExposure":"public","authReady":true,"bootstrapStatus":"ready","bootstrapInviteActive":false}
Desktop parser simulation result:
{
"accepted": true,
"status": "ok",
"deploymentMode": "authenticated",
"deploymentExposure": "public",
"authReady": true,
"bootstrapStatus": "ready",
"bootstrapInviteActive": false
}
Suggested fixes
Possible server-side fix:
- Ensure Paperclip server
2026.609.0 includes authReady in /api/health whenever it includes deploymentExposure for authenticated deployments.
Possible Desktop-side fix:
- Accept the current server response shape for authenticated deployments where:
status === "ok"
deploymentMode === "authenticated"
deploymentExposure is "public" or "private"
bootstrapStatus and bootstrapInviteActive are present
authReady is absent
- Or show a more precise error such as:
Health endpoint is reachable, but response is missing authReady.
Why this matters
The current Desktop error suggests a networking/public accessibility problem. That sends users toward DNS, Cloudflare, TLS, and firewall debugging, even though the actual issue is a schema mismatch between the server's /api/health response and Desktop's parser.
Server/origin:
- Paperclip CLI/server package:
paperclipai@2026.609.0
paperclipai --version: 2026.609.0
@paperclipai/server: 2026.609.0 via global paperclipai package
- Latest npm
paperclipai at time of testing: 2026.609.0
- Node.js:
v22.22.1
- npm:
9.2.0
- OS:
Ubuntu 26.04 LTS x86_64
- Nginx:
nginx/1.28.3 (Ubuntu)
- Deployment mode: authenticated
- Deployment exposure: public
- Paperclip server bound to:
127.0.0.1:3100
- Reverse proxy: Nginx, public HTTPS through Cloudflare
Desktop:
- Paperclip Desktop latest release observed:
v3.2.9
- Repository:
https://github.com/aronprins/paperclip-desktop
- Desktop remote preflight code appears to probe:
/api/health
/api/auth/get-session
Privacy checklist
Pre-submission checklist
master).What happened?
Paperclip Desktop remote connection preflight reports that a remote Paperclip server does not expose the health endpoint / is not a Paperclip host when connecting to an authenticated public Paperclip server whose
/api/healthresponse omitsauthReady.The health endpoint is publicly reachable and returns HTTP 200 JSON, but the Desktop preflight parser rejects the response because it contains
deploymentExposure: "public"while omittingauthReady.This creates a confusing user-facing error: the endpoint is reachable, but Desktop says the health endpoint is not accessible or the host does not appear to expose it.
Original failing health response
Before the workaround, the public health endpoint was reachable and returned HTTP 200:
Response body:
{ "status": "ok", "deploymentMode": "authenticated", "deploymentExposure": "public", "bootstrapStatus": "ready", "bootstrapInviteActive": false }Important detail:
authReadyis missing.Session probe response
The auth/session probe returns the expected signed-out response:
Response:
{"error":"Board authentication required"}This appears to be expected and is handled by Desktop as
signed_outwhen the health response passes validation.Expected behavior
Desktop should accept this host as a valid authenticated Paperclip remote, or the server should include
authReadyin its public authenticated health response.A valid authenticated/public health response should look like:
{ "status": "ok", "deploymentMode": "authenticated", "deploymentExposure": "public", "authReady": true, "bootstrapStatus": "ready", "bootstrapInviteActive": false }Steps to reproduce
Follow the VPS install guide in the docs
Paperclip version or commit
2026.609.0
Deployment mode
Self-hosted server
Installation method
npm / pnpm global install
Agent adapter(s) involved
Database mode
None
Access context
None
Node.js version
No response
Operating system
No response
Relevant logs or output
Relevant config (if applicable)
Additional context
Actual behavior
Paperclip Desktop rejects the remote during preflight with a message equivalent to:
However,
/api/healthis publicly accessible and returns HTTP 200 JSON.Root cause observed
In Paperclip Desktop
src/connection/preflight.ts, the health parser accepts either:deploymentExposure !== nullauthReady !== nullor:
status === "ok"deploymentMode === "authenticated"bootstrapStatus !== nullbootstrapInviteActive !== nulldeploymentExposure === nullauthReady === nullThe server response is neither shape:
deploymentExposureis present:"public"authReadyis absent/nullSo
parseHealthPayload()returnsnull, and Desktop reportsnot_paperclip/ health endpoint unavailable even though the endpoint is reachable.Pseudo-simulation of the failing shape:
Relevant config (sanitized)
/home/paperclip/paperclip.env:Relevant parts of
/home/paperclip/.paperclip/instances/default/config.json:{ "database": { "mode": "postgres", "connectionString": "REDACTED" }, "server": { "deploymentMode": "authenticated", "exposure": "public", "bind": "loopback", "host": "127.0.0.1", "port": 3100, "allowedHostnames": [ "paperclip.example.com" ], "serveUi": true }, "auth": { "baseUrlMode": "explicit", "disableSignUp": false, "publicBaseUrl": "https://paperclip.example.com" }, "storage": { "provider": "local_disk" } }Relevant Nginx reverse proxy shape before workaround:
Workaround used successfully
I applied a narrow Nginx workaround for only
/api/healthto inject"authReady": trueinto the JSON response.Nginx location block:
After reloading Nginx, the public health endpoint returns:
{ "status": "ok", "deploymentMode": "authenticated", "deploymentExposure": "public", "authReady": true, "bootstrapStatus": "ready", "bootstrapInviteActive": false }Desktop then accepts the remote.
Verification after workaround
returns:
{"status":"ok","deploymentMode":"authenticated","deploymentExposure":"public","authReady":true,"bootstrapStatus":"ready","bootstrapInviteActive":false}Desktop parser simulation result:
{ "accepted": true, "status": "ok", "deploymentMode": "authenticated", "deploymentExposure": "public", "authReady": true, "bootstrapStatus": "ready", "bootstrapInviteActive": false }Suggested fixes
Possible server-side fix:
2026.609.0includesauthReadyin/api/healthwhenever it includesdeploymentExposurefor authenticated deployments.Possible Desktop-side fix:
status === "ok"deploymentMode === "authenticated"deploymentExposureis"public"or"private"bootstrapStatusandbootstrapInviteActiveare presentauthReadyis absentHealth endpoint is reachable, but response is missing authReady.Why this matters
The current Desktop error suggests a networking/public accessibility problem. That sends users toward DNS, Cloudflare, TLS, and firewall debugging, even though the actual issue is a schema mismatch between the server's
/api/healthresponse and Desktop's parser.Server/origin:
paperclipai@2026.609.0paperclipai --version:2026.609.0@paperclipai/server:2026.609.0via globalpaperclipaipackagepaperclipaiat time of testing:2026.609.0v22.22.19.2.0Ubuntu 26.04 LTS x86_64nginx/1.28.3 (Ubuntu)127.0.0.1:3100Desktop:
v3.2.9https://github.com/aronprins/paperclip-desktop/api/health/api/auth/get-sessionPrivacy checklist