louislam/uptime-kuma · error · Error
Remote Browser not found
Error message
Remote Browser not found
What it means
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.
Source
Thrown at server/remote-browser.js:59
bean.name = remoteBrowser.name;
bean.url = remoteBrowser.url;
await R.store(bean);
return bean;
}
/**
* Delete a Remote Browser
* @param {number} remoteBrowserID ID of the Remote Browser to delete
* @param {number} userID ID of the user who created the Remote Browser
* @returns {Promise<void>}
*/
static async delete(remoteBrowserID, userID) {
let bean = await R.findOne("remote_browser", " id = ? AND user_id = ? ", [remoteBrowserID, userID]);
if (!bean) {
throw new Error("Remote Browser not found");
}
// Delete removed remote browser from monitors if exists
await R.exec("UPDATE monitor SET remote_browser = null WHERE remote_browser = ?", [remoteBrowserID]);
await R.trash(bean);
}
}
module.exports = {
RemoteBrowser,
};
View on GitHub (pinned to 6b5ea01557)
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.
Example fix
// before
await RemoteBrowser.delete(id, userID);
// after
try {
await RemoteBrowser.delete(id, userID);
} catch (e) {
if (/Remote Browser not found/.test(e.message)) return; // already gone, idempotent
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Make delete idempotent: check existence, tolerate already-gone
const exists = await R.findOne('remote_browser', ' id = ? AND user_id = ? ', [Number(id), userID]);
if (!exists) return { ok: true, msg: 'already deleted' }; Type guard
function isDeletableId(id, userID) {
return Number.isInteger(Number(id)) && Number.isInteger(userID);
} Try / catch
try {
await RemoteBrowser.delete(id, userID);
} catch (e) {
if (/Remote Browser not found/.test(e.message)) return; // idempotent
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Remote browser not found
- notification not found
- proxy not found
- docker host not found
- Monitor not found or not active.
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/afb4b782056a1838.
Report an issue: GitHub.