paperclipai/paperclip · error

Environment variable

Error message

Environment variable ${envName} is empty or not set.

What it means

HTTP 404 with body {"error":"User secret definition not found"} from PATCH /api/companies/:companyId/user-secret-definitions/:definitionId (secrets.ts:658). svc.updateUserSecretDefinition(companyId, definitionId, ...) returned null: no updatable definition matched the (companyId, definitionId) pair - the definition was deleted, its status was already terminal (e.g. a prior update set status 'deleted'), it belongs to another company, or it never existed. Route requires secret-definition admin (assertSecretDefinitionAdmin) and the company must match the definition.

Solutions

  1. Re-list the company's user secret definitions and confirm the definitionId is still present and not already deleted.
  2. If you intended deletion, do not PATCH afterwards - deletion is terminal through this route; recreate the definition instead.
  3. Verify :companyId matches the company the definition actually lives in.
  4. On race with another admin, re-fetch, re-apply the surviving fields, and surface a conflict to the user instead of retrying.

Example fix

// before
await api.patch(`/api/companies/${companyId}/user-secret-definitions/${defId}`, { status: 'active' });

// after
const defs = await api.listUserSecretDefinitions(companyId);
if (!defs.some((d) => d.id === defId && d.status !== 'deleted')) {
  throw new Error(`definition ${defId} missing, cross-company, or already deleted`);
}
await api.patch(`/api/companies/${companyId}/user-secret-definitions/${defId}`, { status: 'active' });
Defensive patterns

Strategy: validation

Validate before calling

async function patchDefinitionSafe(api: ApiClient, companyId: string, definitionId: string, patch: unknown) {
  const defs = await api.fetch(`/api/companies/${companyId}/user-secret-definitions`);
  const list = await defs.json();
  const target = (Array.isArray(list) ? list : list.items ?? []).find(
    (d: { id: string; status?: string }) => d.id === definitionId && d.status !== 'deleted',
  );
  if (!target) throw new Error(`definition ${definitionId} missing or already deleted`);
  return api.fetch(`/api/companies/${companyId}/user-secret-definitions/${definitionId}`, {
    method: 'PATCH',
    body: JSON.stringify(patch),
  });
}

Type guard

function isApiErrorBody(body: unknown): body is { error: string } {
  return typeof body === 'object' && body !== null &&
    typeof (body as Record<string, unknown>).error === 'string';
}
const isDefinitionNotFound = (b: unknown): boolean =>
  isApiErrorBody(b) && b.error === 'User secret definition not found';

Try / catch

try {
  await api.patch(`/api/companies/${companyId}/user-secret-definitions/${defId}`, patch);
} catch (err) {
  if (err instanceof ApiError && err.status === 404 && isDefinitionNotFound(err.body)) {
    await reloadDefinitions(companyId); // deleted concurrently or wrong company
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Patching a definition that was concurrently deleted by another admin; setting status to 'deleted' in one request and then patching it again; using a definitionId from a different company than :companyId; definition IDs stale after a reseed or after definitions were migrated.

Common situations: Admin UI forms kept open across a definition removal; automation that 'disables then edits' definitions in quick succession; copy/paste of definition IDs between staging and production tenants.

Related errors


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/a2877d88bd0e6420. Report an issue: GitHub.

Appendix: source

Thrown at cli/src/commands/client/auth.ts:200

            handleCommandError(err);
          }
        }),
    );
  }
}

function parseJson(value: string): unknown {
  return JSON.parse(value) as unknown;
}

function resolveChallengeToken(opts: AuthChallengeOptions): string {
  const token = opts.token?.trim();
  if (token) return token;
  const envName = opts.tokenEnv?.trim();
  if (envName) {
    const envValue = process.env[envName]?.trim();
    if (envValue) return envValue;
    throw new Error(`Environment variable ${envName} is empty or not set.`);
  }
  throw new Error("Challenge secret is required. Pass --token or --token-env.");
}

View on GitHub (pinned to 120ae5428f)