paperclipai/paperclip · error
Challenge secret is required. Pass --token or --token-env.
Error message
Challenge secret is required. Pass --token or --token-env.
What it means
HTTP 404 with body {"error":"User secret definition not found"} from DELETE /api/companies/:companyId/user-secret-definitions/:definitionId (secrets.ts:691). svc.removeUserSecretDefinition(companyId, definitionId, ...) returned null: no definition matched the company+ID pair at removal time - already deleted, wrong company, or bad ID. Requires secret-definition admin rights.
Solutions
- Treat 404 on DELETE as 'already gone' when removal is the goal (idempotent semantics).
- Derive deletion targets from a fresh list call scoped to :companyId, not from stored IDs.
- Confirm the actor holds secret-definition admin in that company before scripting bulk deletes.
- Verify :companyId and definitionId pair correctness when copying between environments.
Example fix
// before
const res = await api.delete(`/api/companies/${companyId}/user-secret-definitions/${defId}`);
if (!res.ok) throw new Error('delete failed');
// after
const res = await api.delete(`/api/companies/${companyId}/user-secret-definitions/${defId}`);
if (res.status === 404) {
logger.info(`definition ${defId} already absent in ${companyId}; done`);
} else if (!res.ok) {
throw new Error(`delete failed: ${res.status}`);
} Defensive patterns
Strategy: fallback
Validate before calling
async function deleteDefinitionSafe(api: ApiClient, companyId: string, definitionId: string) {
const res = await api.fetch(`/api/companies/${companyId}/user-secret-definitions/${definitionId}`, {
method: 'DELETE',
});
if (res.status === 404) return { deleted: true, alreadyGone: true };
if (!res.ok) throw new Error(`delete failed: ${res.status}`);
return { deleted: true, alreadyGone: false };
} 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.delete(`/api/companies/${companyId}/user-secret-definitions/${defId}`);
} catch (err) {
if (err instanceof ApiError && err.status === 404 && isDefinitionNotFound(err.body)) {
return; // already removed - idempotent success
}
throw err;
} Prevention
- Treat definition DELETE 404 as success in deprovisioning scripts.
- Derive deletion targets from a fresh, company-scoped list call.
- Match :companyId to the definition's real owner when copying IDs.
- Make UI delete actions idempotent (disable after click, ignore follow-up 404s).
When it happens
Trigger: Double-delete of the same definition; deleting a definition whose ID was copied from another company; cleanup jobs re-running after a partial failure where the first pass already removed it.
Common situations: Idempotency-unaware deprovisioning scripts; multi-company admins pasting a definitionId under the wrong :companyId; UI retry of a delete that actually succeeded.
Related errors
- Cannot build API path with an empty path segment.
- Invalid JSON
- Environment variable
- Company ID is required. Pass --company-id, set…
- Could not locate local Paperclip skills directory. Expected…
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/c677f163b6b96e91.
Report an issue: GitHub.
Appendix: source
Thrown at cli/src/commands/client/auth.ts:202
}),
);
}
}
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)