koala73/worldmonitor · error · ConvexError
NOT_FOUND
Error message
NOT_FOUND
What it means
Thrown by the updateCompanyForOwner mutation when the target company cannot be patched. The lookup uses the by_account_companyId index scoped to the caller's logical account, so the row must belong to the authenticated owner. The guard also rejects companies whose lifecycle is 'removed' or whose required display fields (name, domicileCountry) have been purged, because a removed company is asynchronously purged and is no longer editable.
Source
Thrown at convex/companyMonitoring/companies.ts:202
},
});
export const updateCompanyForOwner = internalMutation({
args: {
ownerUserId: v.string(),
companyId: v.string(),
patch: companyPatchValidator,
},
handler: async (ctx, args) => {
const account = await requireActiveAccount(ctx, args.ownerUserId);
const company = await ctx.db
.query("companyMonitoringCompanies")
.withIndex("by_account_companyId", (q) =>
q.eq("ownerAccountId", account.logicalAccountId).eq("companyId", args.companyId),
)
.unique();
if (!company || company.lifecycle === "removed" || !company.name || !company.domicileCountry) {
throw new ConvexError("NOT_FOUND");
}
const patch = args.patch;
const addClaimInputs = patch.addClaims ?? [];
const removeClaimInputs = patch.removeClaimIds ?? [];
if (
addClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany ||
removeClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany
) {
throw new ConvexError("INVALID_COMPANY_PATCH");
}
const hasName = Object.prototype.hasOwnProperty.call(patch, "name");
const hasDomicile = Object.prototype.hasOwnProperty.call(patch, "domicileCountry");
const hasCustomerReference = Object.prototype.hasOwnProperty.call(patch, "customerReference");
const normalizedFields = normalizeMonitoredCompanyInput({
name: hasName ? patch.name! : company.name,
domicileCountry: hasDomicile ? patch.domicileCountry! : company.domicileCountry,
customerReference: hasCustomerReferenceView on GitHub (pinned to ffec79ac33)
Solutions
- Verify the companyId still exists and is active/paused by listing via listCompaniesForOwner before attempting the update.
- If the company was removed, create a new company with createCompanyForOwner instead of patching the removed row.
- Handle NOT_FOUND in the caller by refreshing the company list and dropping the stale reference from local state.
Example fix
// before
await ctx.runMutation(internal.companyMonitoring.companies.updateCompanyForOwner, {
ownerUserId, companyId: staleId, patch,
});
// after
const companies = await ctx.runQuery(internal.companyMonitoring.companies.listCompaniesForOwner, { ownerUserId });
if (!companies.some((c) => c.companyId === staleId)) {
return { status: "gone" };
}
await ctx.runMutation(internal.companyMonitoring.companies.updateCompanyForOwner, {
ownerUserId, companyId: staleId, patch,
}); Defensive patterns
Strategy: validation
Validate before calling
const companies = await ctx.runQuery(internal.companyMonitoring.companies.listCompaniesForOwner, { ownerUserId });
const exists = companies.some((c) => c.companyId === companyId);
if (!exists) throw new UserError("Company no longer exists");
await ctx.runMutation(internal.companyMonitoring.companies.updateCompanyForOwner, { ownerUserId, companyId, patch }); Type guard
function isEditableCompany(row: { lifecycle: string; name?: string; domicileCountry?: string }): boolean {
return row.lifecycle !== "removed" && Boolean(row.name) && Boolean(row.domicileCountry);
} Try / catch
try {
await updateCompanyForOwner(...);
} catch (err) {
if (err instanceof ConvexError && err.message === "NOT_FOUND") {
// refresh company list, drop stale id
} else throw err;
} Prevention
- Do not cache companyId across sessions without re-validating via listCompaniesForOwner.
- Disable edit controls once a company enters the removed lifecycle.
When it happens
Trigger: Calling updateCompanyForOwner with a companyId that does not exist for this account, that belongs to a different account, that was previously set to 'removed' via setCompanyStateForOwner, or whose async purge (advanceCompanyPurge) has already nulled name/domicileCountry.
Common situations: Stale companyId held in the UI after the user removed the company in another session; race between a remove action and an in-flight update; typo or copy/paste of an ID from a different account; calling update on a company mid-purge.
Related errors
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/f874fde33a9f914d.
Report an issue: GitHub.