{"record":{"id":"afb4b782056a1838","repo":"louislam/uptime-kuma","slug":"remote-browser-not-found-afb4b7","errorCode":null,"errorMessage":"Remote Browser not found","messagePattern":"Remote Browser not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/remote-browser.js","lineNumber":59,"sourceCode":"        bean.name = remoteBrowser.name;\n        bean.url = remoteBrowser.url;\n\n        await R.store(bean);\n\n        return bean;\n    }\n\n    /**\n     * Delete a Remote Browser\n     * @param {number} remoteBrowserID ID of the Remote Browser to delete\n     * @param {number} userID ID of the user who created the Remote Browser\n     * @returns {Promise<void>}\n     */\n    static async delete(remoteBrowserID, userID) {\n        let bean = await R.findOne(\"remote_browser\", \" id = ? AND user_id = ? \", [remoteBrowserID, userID]);\n\n        if (!bean) {\n            throw new Error(\"Remote Browser not found\");\n        }\n\n        // Delete removed remote browser from monitors if exists\n        await R.exec(\"UPDATE monitor SET remote_browser = null WHERE remote_browser = ?\", [remoteBrowserID]);\n\n        await R.trash(bean);\n    }\n}\n\nmodule.exports = {\n    RemoteBrowser,\n};\n","sourceCodeStart":41,"sourceCodeEnd":72,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/remote-browser.js#L41-L72","documentation":"Thrown by RemoteBrowser.delete() when no `remote_browser` row matches the given `remoteBrowserID` AND `userID`. Note the message uses a capital 'B' ('Remote Browser not found'), differing from errors 200/201. The method cleans up monitor references before trashing, but aborts if the target is missing or not owned.","triggerScenarios":"A delete request for an already-deleted remote browser, an ID owned by another user, or a stale ID from the client. Because the monitor `UPDATE ... SET remote_browser = null` cleanup runs only after the existence check, a missing row means monitors keep their stale reference.","commonSituations":"User clicks delete twice rapidly; deletion of a row that was removed in another session; cross-tenant delete attempt; frontend sends an ID from a cached list that is out of date.","solutions":["Make the delete idempotent in the handler: catch 'Remote Browser not found' and return success (it is already gone).","Verify ownership/existence before calling delete() if you need to distinguish 'was deleted' from 'never existed'.","Refresh the client list after delete so the stale ID is not re-sent.","Coerce the ID to an integer before lookup."],"exampleFix":"// before\nawait RemoteBrowser.delete(id, userID);\n\n// after\ntry {\n    await RemoteBrowser.delete(id, userID);\n} catch (e) {\n    if (/Remote Browser not found/.test(e.message)) return; // already gone, idempotent\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Make delete idempotent: check existence, tolerate already-gone\nconst exists = await R.findOne('remote_browser', ' id = ? AND user_id = ? ', [Number(id), userID]);\nif (!exists) return { ok: true, msg: 'already deleted' };","typeGuard":"function isDeletableId(id, userID) {\n  return Number.isInteger(Number(id)) && Number.isInteger(userID);\n}","tryCatchPattern":"try {\n  await RemoteBrowser.delete(id, userID);\n} catch (e) {\n  if (/Remote Browser not found/.test(e.message)) return; // idempotent\n  throw e;\n}","preventionTips":["Make delete handlers idempotent by swallowing the not-found case.","Guard against double-delete from rapid UI clicks with a debounce.","Coerce IDs to integers at the request boundary.","Refresh the client list after a successful delete."],"tags":["crud","not-found","ownership","remote-browser","idempotency"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}