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

  1. Make the delete idempotent in the handler: catch 'Remote Browser not found' and return success (it is already gone).
  2. Verify ownership/existence before calling delete() if you need to distinguish 'was deleted' from 'never existed'.
  3. Refresh the client list after delete so the stale ID is not re-sent.
  4. 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

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


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/afb4b782056a1838. Report an issue: GitHub.