{"slug": "my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on", "title": "My (seemingly successful?) attempt to root-cause the severe CPU/RAM usage issues and multi-second jankiness for GitHub.com on Firefox.", "summary": "A developer investigating severe CPU and memory problems on GitHub.com in Firefox traced the runaway memory growth and multi-second jank primarily to GitHub's DataDog real-user-monitoring script, with global keybindings contributing to a lesser degree. Profiling on Firefox 158.0.1 under Fedora 44 showed long sessions with roughly 50 tabs reaching tens of gigabytes of memory and pinning processes near 100% CPU, sometimes ending in out-of-memory crashes; an attempt to have Claude analyze the profiler and GC logs was inconclusive due to log volume and lack of a reliable reproduction. The developer published TamperMonkey user scripts intended to mitigate the issue.", "body_md": "**Note:** I didn't use AI to write this. I originally had it try to analyze the profiler GC logs, but there wasn't enough information for it to be conclusive (see below).\n\nThis information is accurate as of 2026-10-29 on Firefox 158.0.1, Fedora 44.\n\nI didn't test this on Chrome.\n\n**TLDR:** I'm pretty sure it's their DataDog RUM, plus, to a much lesser degree, global keybindings. See [below](#neutering-it) for some TamperMonkey user scripts you can try.\n\nFor the last little while (at least a few weeks, at most a year, don't remember exactly when it got this bad), I've been having performance issues on GitHub. It's been stuttery in general, with multi-second periods of jank every now and then, and with memory usage steadily increasing over time.\n\nWith long-running sessions and ~50 tabs, I've seen it go into tens of gigabytes of memory usage, while pinning the corresponding process at 100% CPU (or close) for extended periods of time.\n\nA few times, I've even had it get so out of hand that the GC couldn't keep up and it ended up OOMing.\n\nI finally ended up running the profiler to try and track it down...\n\nThis screenshot is just one example from today, a few minutes after opening a GitHub page, of runaway memory and GC jank on a GitHub page while doing nothing but scrolling around on it.\n\nBefore I did this investigation, I had half-heartedly searched for causes.\n\nI suspected it was Bitwarden at first given its history of causing performance issues in pages (t still does, it's just nothing compared to this). For example, as of the current version of the Bitwarden extension (2026.10.0), even when autofill is off, it scans the entire page multiple times in quite an inefficient manner (it keeps re-querying backwards to find headers on elements rather than doing it all in a few passes). But, after disabling Bitwarden, I realized that it's an issue with GitHub or Firefox.\n\nI then suspected it may be changes in Firefox 157/158 given that there was anecdotal evidence of this on various Reddit threads, but everyone attributed it to a different cause and different fix ([1](https://www.reddit.com/r/firefox/comments/1wwdup4/i_updated_to_157_and_i_have_severe_performance/), [2](https://www.reddit.com/r/firefox/comments/1wwzwdq/firefox_1570_has_some_serious_memory_leaks/))... and, of the people who provided more detail, GitHub was a common factor.\n\nLooking specifically at GitHub and Firefox, I found a few threads ([1](https://connect.mozilla.org/t5/discussions/firefox-feels-slower-overall-especially-on-github/m-p/143093), [2](https://github.com/orgs/community/discussions/27334), etc) and some stuff on bugzilla ([1](https://bugzilla.mozilla.org/show_bug.cgi?id=2060501), [2](https://bugzilla.mozilla.org/show_bug.cgi?id=2059903)). The second bugzilla bug claimed it was a GitHub issue with `selectionchange`, which was claimed to have later been fixed, but it definitely isn't the only thing.\n\nI tried passing Claude the performance profile I captured, and GC/CC logs from `about:memory`, but there wasn't enough information for it to be conclusive.\n\nThis was partly caused by Firefox dropping stuff due to the sheer volume (*one* of the *concise* CC logs for a single process was already multiple gigabytes, and each GC was collecting millions of objects), but also because I hadn't found a reliable way to reproduce it on-demand.\n\nI abandoned this approach after a bit.\n\nI ultimately ended up making educated guesses myself based on the JS traces during the periods of increasing memory and jank, then trying them out. I started with two obvious things which stood out in the JS allocation flame graph during an extended period of jank and memory usage increase (this particular one was while typing out the body of a GitHub release): global keybindings, and the DataDog RUM.\n\nI also suspect this may be related to or exacerbated by the client-side navigation keeping objects (elements, event handlers, closures, etc) alive.\n\nI decided to start digging into the referenced JS chunks to determine the plausiblity of these theories.\n\n**TLDR:** Even if you have \"Character keys\" disabled, every keypress (including in text fields) results in rebuilding the keybinding map from scratch, and triggering multple page layouts while searching for modals.\n\n`GlobalCommands` is in chunk `gw-4bf343b417f9ec19.js`... this is the relevant snippet (with a few additional comments):\n\n``` js\nconst GlobalCommands = ({ commands }) => {\n  const ref = useRef(null)\n  const onTrigger = useEffectEvent(handleTriggerEvent) // k\n  const handle = useKeybindingHandler(runGlobalCommand) // mF.r(h)\n  const onKeyDown = useEffectEvent(e => {\n    const modal = topmostModalCached(e) // rG.ck, cached per event\n    if ((modal && !modal.contains(ref.current)) || alreadySeen(e)) return\n    handle(e, getKeybindingMap(globalCommandIds())) // RIB.fg(lS.hd())\n  })\n  useKeybindingConflictWarnings(\"global\", commands) // gt.A\n  useRegisterCommands(\"global\", commands) // lS.JT\n  useEffect(() => {\n    const ac = new AbortController()\n    document.addEventListener(\"keydown\", onKeyDown, { signal: ac.signal })\n    document.addEventListener(\"ui-command-trigger\", onTrigger, { signal: ac.signal })\n    return () => ac.abort()\n  }, [])\n  return <div ref={ref} className=\"d-none\" />\n}\n```\n\nSo for each keydown ANYWHERE on the page (including inside textboxes):\n\n- All mounted instances of `GlobalCommands` get called.\n- The first instance that passes the modal check does most of the logic:\n  - `topmostModal()` runs`querySelectorAll('dialog:modal, [role=\"dialog\"][aria-modal=\"true\"]')` over the whole document, then reads`clientHeight` recursively on each match.\n    - This is already definitely relevant since reading `clientHeight` forces a layout.\n  - This is already definitely relevant since reading \n  - `getKeybindingMap()` is rebuilt from scratch.\n    - This is somewhat expensive since it involves, for each registered command, a feature-flag lookup, regexes on the binding, and a parse into a `Map` .\n  - This is somewhat expensive since it involves, for each registered command, a feature-flag lookup, regexes on the binding, and a parse into a \n  - Then, after doing all that work, it checks if keyboard shortcuts are enabled and that the user isn't within a text input.\n- An analytics event gets sent when a command matches.\n\nNevertheless, this isn't enough to cause the level of jankiness and memory usage I've been running into.\n\nNext, I looked at DataDog. I didn't bother to figure out exactly which part was causing it since it's obvious it's one of the main issues either way.\n\nIronically, one of the things GitHub uses DataDog for is to track performance, but it's DataDog itself likely *causing* those performance issues.\n\nThis the current slightly abbreviated `<script id=\"datadog-rum-config\">`:\n\n```\ndatadogRum.init({\n  applicationId: config.applicationId,\n  clientToken: config.clientToken,\n  site: config.site,\n  service: config.service,\n  env: config.env,\n  version: document.head.querySelector('meta[name=\"release\"]')?.content ?? \"\",\n  proxy: ({ path, parameters, subdomain }) => proxyUrl(config.proxyUrl, path, parameters, subdomain), // ?ddforward=...\n  workerUrl: \"/assets-cdn/worker/chunk-m-*.js\",\n  ...(config.remoteConfiguration ? { remoteConfiguration: { id: config.remoteConfiguration.id }, remoteConfigurationProxy: ... } : {}),\n  profilingSampleRate: config.profilingSampleRate,\n  sessionSampleRate: config.sessionSampleRate,\n  sessionReplaySampleRate: 0,\n  enableExperimentalFeatures: [\"feature_flags\"],\n  compressIntakeRequests: true,\n  telemetrySampleRate: 0,\n  defaultPrivacyLevel: \"mask-user-input\",\n  useSecureSessionCookie: location.protocol === \"https:\",\n  trackAnonymousUser: false,\n  trackViewsManually: true,\n  trackUserInteractions: true,\n  enablePrivacyForActionName: true,\n  trackResources: true,\n  allowedTracingUrls: [{ match: sameOriginExceptRaw, propagatorTypes: [\"tracecontext\"] }],\n  allowedGraphQlUrls: [{ match: isGraphQL, trackPayload: false, trackResponseErrors: false }],\n  trackLongTasks: true,\n  beforeSend: scrubUrlsAndDropCachedImages,\n  plugins: [reactPlugin({ router: false })],\n  silentMultipleInit: true,\n});\n```\n\nThe `trackViewsManually` is used to track views on the client-side navigation GitHub does.\n\nFor me, while logged in, on a repository page, the `sessionSampleRate` was 100%, and the `profilingSampleRate` was 5%, with no `remoteConfiguration` (see my userscript below)\n\nAlso, interestingly, I don't see DataDog listed as a subprocessor in the GitHub docs ([https://docs.github.com/en/site-policy/privacy-policies/github-subprocessors](https://docs.github.com/en/site-policy/privacy-policies/github-subprocessors))...\n\nI wrote some TamperMonkey userscripts to block the problematic thingies.\n\nRestart firefox, or at least kill all the GitHub processes in `about:performance` after installing these. I also recommend disabling Bitwarden and other heavy extensions to avoid confounding things.\n\nWe can match it based on an `addEventListener` for a `document`-level `keydown` event preceding the custom `ui-command-trigger` event.\n\n```\n// ==UserScript==\n// @name         GitHub: remove GlobalCommands keydown listeners\n// @namespace    https://pgaskin.net\n// @supportURL   https://gist.github.com/pgaskin/9f1445e8261f488a50498ea1b98272e8\n// @updateURL    https://gist.github.com/pgaskin/9f1445e8261f488a50498ea1b98272e8/raw/github-neuter-global-keybindings.user.js\n// @version      1.0\n// @match        https://github.com/*\n// @run-at       document-start\n// @grant        none\n// @sandbox      JavaScript\n// @inject-into  page\n// ==/UserScript==\n\n(() => {\n    \"use strict\";\n\n    const add = EventTarget.prototype.addEventListener;\n    const remove = EventTarget.prototype.removeEventListener;\n    const pending = new WeakMap();\n\n    EventTarget.prototype.addEventListener = function addEventListener(type, listener, options) {\n        if (this === document && options?.signal instanceof AbortSignal) {\n            if (type === \"keydown\") {\n                pending.set(options.signal, listener);\n            } else if (type === \"ui-command-trigger\") {\n                const kd = pending.get(options.signal);\n                if (kd) {\n                    pending.delete(options.signal);\n                    remove.call(document, \"keydown\", kd);\n                }\n            }\n        }\n        return add.call(this, type, listener, options);\n    }\n})();\n```\n\nThe proper fix would be for GitHub to avoid registering the events in the first place if global keybindings are disabled, and to cache the keybinding map, only rebuilding it when it changes.\n\nRegardless, this is only a small (but still noticeable) portion of the jank. GitHub should still fix it, though, as it's a relatively small change.\n\n```\n// ==UserScript==\n// @name         GitHub: log/disable Datadog RUM\n// @namespace    https://pgaskin.net\n// @supportURL   https://gist.github.com/pgaskin/9f1445e8261f488a50498ea1b98272e8\n// @updateURL    https://gist.github.com/pgaskin/9f1445e8261f488a50498ea1b98272e8/raw/github-neuter-datadog.user.js\n// @version      1.0\n// @match        https://github.com/*\n// @run-at       document-start\n// @grant        none\n// ==/UserScript==\n\n(() => {\n    \"use strict\";\n\n    // set this to true to log the datadog params, but still let it run\n    const LOG_ONLY = false;\n\n    // setupDatadogRum currently won't import the chunks if #datadog-rum-config is missing\n    // note: it's retried every call, so we need to keep observing across client-side navigations\n    let last;\n    const seen = new WeakSet();\n    const hook = () => {\n        const el = document.getElementById(\"datadog-rum-config\");\n        if (!el || seen.has(el)) {\n            return;\n        }\n        seen.add(el);\n\n        if (!LOG_ONLY) {\n            el.remove();\n        }\n\n        try {\n            const {\n                sessionSampleRate,\n                profilingSampleRate,\n                remoteConfiguration,\n            } = JSON.parse(el.textContent);\n\n            const msg = `${LOG_ONLY ? \"found\" : \"removed\"} datadog-rum-config (sessionSampleRate=${sessionSampleRate}, profilingSampleRate=${profilingSampleRate}, remoteConfiguration=${remoteConfiguration?.id ?? \"none\"})`;\n            if (msg !== last) {\n                console.warn(\"[github-datadog]\", (last = msg));\n            }\n        } catch (err) {\n            console.warn(`[github-datadog] ${LOG_ONLY ? \"found\" : \"removed\"} datadog-rum-config, but couldn't parse it`, err);\n        }\n    };\n    new MutationObserver(hook).observe(document, {\n        childList: true,\n        subtree: true,\n    });\n    hook();\n\n    // I had claude write this part for me based on the published SDK (v7) so I didn't have to reverse it myself\n    for (const name of [\"_dd_s_v2\", \"_dd_s\"]) {\n        const raw = document.cookie.split(\"; \").find((c) => c.startsWith(name + \"=\"))?.slice(name.length + 1);\n        if (!raw) {\n            console.warn(`[github-datadog] ${name}: not set`);\n            continue;\n        }\n        const s = Object.fromEntries(decodeURIComponent(raw).split(\"&\").map((kv) => kv.split(\"=\", 2)));\n        const expire = Number(s.expire);\n        const live = !!s.id && !s.isExpired && expire > Date.now();\n        console.warn(\n            `[github-datadog] ${name}: ${live ? \"live RUM session\" : \"no live session\"}` +\n            (s.id ? `, created ${new Date(Number(s.created)).toISOString()}, expires ${new Date(expire).toISOString()}` : \"\") +\n            (s.forcedReplay ? \", forced replay\" : \"\"),\n        );\n    }\n})();\n```\n\nYou can set `LOG_ONLY` to true if you want to confirm that it's enabled for you by default.\n\nNote that you may need to filter the js console since it currently spams (unrelated) `Referrer Policy: Ignoring the less restricted referrer policy “no-referrer-when-downgrade” for the cross-site request: https://github.githubassets.com/assets/...` lines at info level.\n\nI have only been running this for a few hours (about half of it while typing out the first draft of this), but the memory usage is already significantly lower (though still somewhat high), CPU usage remains low, and I haven't had any jankiness yet...\n\nI'll probably open a GitHub Support ticket later if it continues to work well.", "url": "https://wpnews.pro/news/my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on", "canonical_source": "https://gist.github.com/pgaskin/9f1445e8261f488a50498ea1b98272e8", "published_at": "2026-10-29 22:13:44+00:00", "updated_at": "2026-10-30 02:46:38.184796+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["GitHub", "Firefox", "Mozilla", "DataDog", "Bitwarden", "Claude", "Fedora", "TamperMonkey"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on", "markdown": "https://wpnews.pro/news/my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on.md", "text": "https://wpnews.pro/news/my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on.txt", "jsonld": "https://wpnews.pro/news/my-seemingly-successful-attempt-to-root-cause-the-severe-cpu-ram-usage-issues-on.jsonld"}}