nomyo-js/doc/troubleshooting.md
alpha nerd eafab3d9ac
All checks were successful
NYX Security Scan / nyx-scan (pull_request) Successful in 5m43s
feat: align timeout, error types and docs with the Python SDK
Completes the parity work (step 4).

Request timeout now defaults to 900 s, matching Python, instead of 60 s.
Encrypted inference cannot stream, so an entire completion arrives in one
response; a long generation on a busy backend legitimately takes minutes
and was timing out here while succeeding in the Python client.

Error types now distinguish malformed data from integrity failures.
Python raises ValueError for a bad package, a non-200 or unparseable
/pki/public_key, and plaintext that will not parse, reserving
SecurityError for crypto failures. This port wrapped nearly all of it in
SecurityError — so a server sending malformed JSON was reported as an
authentication failure, pointing debugging in exactly the wrong
direction. Malformed data is now a plain Error (the JS equivalent of
ValueError), carried past the deliberately opaque catch-all by a symbol
marker rather than a new exported class. Genuine crypto failures still
report a single vague message so they cannot serve as a decryption
oracle.

Also adds the missing guard Python has: decrypting without a private key
now says so, instead of failing later and being reported as an integrity
failure.

doc/attestation.md ports the Python attestation guide to the JS API, and
documents the two deliberate divergences: no verify_ssl escape hatch, and
jose injection instead of a runtime dynamic import.

Version 0.1.0 -> 0.3.0 to match the Python client's feature level, now
that the two are at parity.

Not ported: Python's warning when secure_memory=True but the SecureMemory
module is unavailable. There is no JS equivalent — zeroing is always
available, and the weaker case (mlock unavailable) is already reported
honestly by getProtectionInfo().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 12:17:52 +02:00

10 KiB

Troubleshooting

Authentication Errors

AuthenticationError: Invalid or missing API key

The server rejected your API key.

Causes and fixes:

  • Key not set — pass apiKey to the constructor or use process.env.NOMYO_API_KEY.
  • Key has leading/trailing whitespace — check the value with console.log(JSON.stringify(process.env.NOMYO_API_KEY)).
  • Key contains CR or LF characters — the client rejects keys with \r or \n and throws SecurityError before the request is sent. Regenerate the key.
const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,  // never hardcode
});

Connection Errors

APIConnectionError: Network error / connect ECONNREFUSED

The client could not reach the router.

Check:

  1. baseUrl is correct — the default is https://api.nomyo.ai (port 12435).
  2. You have network access to the host.
  3. TLS is not being blocked by a proxy or firewall.

SecurityError: HTTPS is required

You passed an http:// URL without setting allowHttp: true.

// Local dev only
const client = new SecureChatCompletion({
  baseUrl: 'http://localhost:12435',
  allowHttp: true,
});

Never set allowHttp: true in production — the server public key fetch and all request data would travel unencrypted.

APIConnectionError: Request timed out

The default timeout is 900 seconds (15 minutes), matching the Python SDK. Encrypted inference cannot stream, so the entire completion arrives in a single response. Lower it if you want to fail faster:

const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
  timeout: 120000,  // 2 minutes
});

Key Loading Failures

Error: Failed to load keys: no such file or directory

The keyDir directory or the PEM files inside it don't exist. This only applies when you opt into persistence — by default keys are ephemeral and no directory is touched at all. On first run with a keyDir set, the library generates and saves a new key pair there automatically, so make sure the directory is writable:

const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
  keyDir: '/var/lib/myapp/nomyo-keys',  // directory must exist and be writable
});

Error: Invalid passphrase / Error: Failed to decrypt private key

The password you passed to loadKeys() or keyRotationPassword doesn't match what was used to encrypt the file.

await client.loadKeys(
  'client_keys/private_key.pem',
  'client_keys/public_key.pem',
  process.env.NOMYO_KEY_PASSWORD,  // must match the password used on generateKeys()
);

Error: RSA key too small

The library enforces a minimum key size of 2048 bits. If you have old 1024-bit keys, regenerate them:

await client.generateKeys({
  saveToFile: true,
  keyDir: 'client_keys',
  keySize: 4096,  // recommended
});

Error: Failed to load keys (browser)

Key loading from files is a Node.js-only feature. In browsers, keys are generated in memory on first use. Do not call loadKeys() in a browser context.


Rate Limit Errors

RateLimitError: Rate limit exceeded

All automatic retries were exhausted. The default limit is 2 requests/second; burst allows 4 requests/second once per 10-second window.

Fixes:

  • Reduce concurrency — avoid large Promise.all batches.
  • Add client-side throttling (see Rate Limits).
  • Increase maxRetries so the client backs off longer before giving up:
const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
  maxRetries: 5,
});

ServiceUnavailableError with 30-minute cool-down

Burst limits were hit repeatedly and a cool-down was applied to your key. Wait 30 minutes, then review your request patterns.


Model / Tier Errors

ForbiddenError: Model not allowed for this security tier

The model you requested is not available at the security tier you specified. Try a lower tier or a different model:

// If 'maximum' tier rejects the model, try 'high' or 'standard'
const response = await client.create({
  model: 'Qwen/Qwen3.5-27B',
  messages: [...],
  security_tier: 'high',  // try 'standard' if still rejected
});

See Models — Security Tier Compatibility for details.


Crypto / Security Errors

SecurityError: Decryption failed

The response could not be decrypted. This is intentionally vague to avoid leaking crypto details.

Possible causes:

  • The server returned a malformed response (check debug: true output).
  • A network proxy modified the response body.
  • The server's public key changed mid-session — the next request will re-fetch it automatically.

Enable debug mode to log the raw response and narrow the cause:

const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
  debug: true,
});

Error: Unsupported protocol version / Error: Unsupported encryption algorithm

The server sent a response in a protocol version or with an encryption algorithm not supported by this client version. Update the package:

npm update nomyo-js

DisposedError: Method called after dispose()

You called a method on a client that has already been disposed.

client.dispose();
await client.create(...);  // throws DisposedError

Create a new client instance if you need to make more requests after disposal.


Memory Protection Warnings

getMemoryProtectionInfo() returns method: 'zero-only'

This is normal for a pure JavaScript installation. The library zeroes sensitive buffers immediately after use but cannot lock pages to prevent swapping (OS mlock requires a native addon).

import { getMemoryProtectionInfo } from 'nomyo-js';

const info = getMemoryProtectionInfo();
// { method: 'zero-only', canLock: false, isPlatformSecure: false }

For environments where swap-file exposure is unacceptable (HIPAA PHI, classified data), install the optional nomyo-native addon or run on a system with swap disabled.

method: 'zero-only' even with the native addon installed

canLock is probed, not assumed — the addon being loaded does not mean the OS will grant locks. mlock is commonly refused by RLIMIT_MEMLOCK, which defaults to a few megabytes and is 0 in some container runtimes. When that happens details says so explicitly, and buffers are still zeroed.

ulimit -l              # current memlock limit in KB; "unlimited" or a large value is what you want

Raise the limit (ulimit -l, systemd LimitMEMLOCK=, or Docker --ulimit memlock=-1), or grant CAP_IPC_LOCK.

Note also that npm 11 gates package install scripts, so a fresh npm ci may skip building the addon entirely until you run npm approve-scripts.


Node.js-Specific Issues

ReferenceError: crypto is not defined

In CommonJS modules on Node.js before v19, crypto is not a global. Import it explicitly:

// CommonJS
const { webcrypto } = require('crypto');
global.crypto = webcrypto;

// Or switch to ES modules (recommended)
// package.json: "type": "module"

The library itself imports crypto correctly — this error only appears if your own application code tries to use crypto directly.

SyntaxError: Cannot use import statement in a module / CommonJS vs ESM

The package ships both CommonJS (dist/node/) and ESM (dist/esm/) builds. Node.js selects the correct one automatically via package.json exports. If you see import errors, check that your package.json or bundler is not forcing the wrong format.

For ESM: set "type": "module" in your package.json or use .mjs file extensions. For CommonJS: use require('nomyo-js') or .cjs extensions.

TypeScript: Cannot find module 'nomyo-js' / missing types

Ensure your tsconfig.json includes "moduleResolution": "bundler" or "moduleResolution": "node16" and that nomyo-js is in dependencies (not just devDependencies):

npm install nomyo-js

Browser-Specific Issues

Content Security Policy blocked

If your app's CSP restricts script-src or connect-src, add the NOMYO API domain:

Content-Security-Policy: connect-src https://api.nomyo.ai;

TypeError: Failed to fetch (CORS)

The NOMYO API includes CORS headers. If you see CORS errors in a browser, verify the baseUrl is correct (HTTPS, correct port) and that no browser extension is blocking the request.

Keys not persisted across page reloads

This is expected behaviour — browsers do not have file system access. Keys are generated fresh on each page load. If you need persistent keys in a browser context, implement your own loadKeys/generateKeys wrapper using localStorage or IndexedDB (not recommended for high-security scenarios).


Debugging Tips

Enable verbose logging

const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
  debug: true,
});

Debug mode logs: key generation/loading, server public key fetches, request encryption details, retry attempts, and response decryption.

Check memory protection status

import { getMemoryProtectionInfo } from 'nomyo-js';
console.log(getMemoryProtectionInfo());

Inspect response metadata

The _metadata field in every response carries server-side diagnostics:

const response = await client.create({ ... });
console.log(response._metadata);
// {
//   payload_id: '...',
//   is_encrypted: true,
//   encryption_algorithm: 'hybrid-aes256-rsa4096',
//   security_tier: 'standard',
//   memory_protection: { ... },
// }

Test with minimum configuration

Strip all optional configuration and test with the simplest possible call to isolate the issue:

import { SecureChatCompletion } from 'nomyo-js';

const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });
const r = await client.create({
  model: 'Qwen/Qwen3-0.6B',
  messages: [{ role: 'user', content: 'ping' }],
});
console.log(r.choices[0].message.content);
client.dispose();