Analyze a Firefox Profiler capture (share.firefox.dev or profiler.firefox.com/public links) for performance issues (jank, long tasks, hangs, high CPU, media playback), producing a structured findings report that other skills can consume. Triggers on "analyze profile", "analyze this profile", "/analyze-profile", or a Firefox Profiler URL.
Analyze a Firefox Profiler capture for performance issues. Produces a structured findings report that other skills can consume.
Invocation: /analyze-profile <url>
Where <url> is a share.firefox.dev/* or profiler.firefox.com/public/* link.
Run printenv GECKO_AGENT_MODE before anything else. If it prints hackbot, you run unattended in the Hackbot cloud container, and these rules override the steps below. Otherwise skip this section: the skill runs as written.
npm install).searchfox-cli: it is not in the container. Name the function or marker that points at the code, so the caller can read it.profiler-cli is too old for the profile, or it is the legacy CLI, report a tooling gap on our side and mark the analysis incomplete. It is not a question for the reporter.share.firefox.dev link or a normal-mode profile): say in your answer which capture is necessary.All skills that encounter a Firefox Profiler URL MUST invoke this skill instead
of running profiler-cli directly. Do not inline profiler-cli commands in any other skill. This skill is the single source of truth for profile
analysis.
Do NOT use WebFetch on profiler URLs — they are JavaScript SPAs; WebFetch returns
only the CSS shell with no profile data.
This skill runs only printenv GECKO_AGENT_MODE, profiler-cli and searchfox-cli.
Use the Read tool for files. Run each command as its own Bash call, with literal values: do not
join commands with ;, && or |, do not set shell variables, and do not cd. The
only joined command is the stop check in the checklist below. Any other joined
command does not match the skill's allowed-tools rules, so it needs approval.
Each profiler-cli load starts a background daemon that holds the whole profile in
memory (often 1–2 GB). It never exits on its own, so a run that does not stop it
leaks that memory until the machine restarts. Follow every item, on every run:
<id> as Step 1 describes, and use only
that id for the whole run.--session <id> on every command. Never rely on the current session.load
said is already running: another run owns it,profiler-cli stop --session <id>; profiler-cli session list | grep -F -- "<id>" && echo "STILL RUNNING: <id>"STILL RUNNING, run it again. If the session is still listed, say so in
the report.Session: <id> stopped. A report
without it is incomplete.profiler-cli stop --all, and never stop
a session id that you did not create.profiler-cli must be on your PATH. If it is missing, ask the user to run
npm install -g @firefox-devtools/profiler-cli.
Command shape (verified against profiler-cli 0.10.0). It is a subcommand CLI and
is stateful: you load once to create a session, then query that session. The old
single-shot forms profiler-cli <url> --calltree N, --top-markers N and
--log-markers no longer exist and will fail.
Name the session yourself, so a leaked one can be traced in profiler-cli session list:
<id> is ap-<bug>-<token>-<n>, where <bug> is the caller's bug number (omit it and
its dash when there is none), <token> is the path segment after share.firefox.dev/ or
the first 12 characters of the hash after profiler.firefox.com/public/, and <n> is a
number. Run profiler-cli session list first, and use the smallest <n> from 1 that no
listed session with the same ap-<bug>-<token>- prefix uses. Do not invent random
digits: they repeat across runs. If load still says the id is already running,
another run took it at the same time, so use the next number.
profiler-cli load <url> --session <id>Then pass --session <id> on every subsequent command:
profiler-cli profile info --all --session <id> # threads + CPU per process
profiler-cli thread list --session <id> # every thread's t-N handle
profiler-cli thread functions --session <id> --thread t-N --min-self 2
profiler-cli thread samples-top-down --session <id> --thread t-N # where CPU goes
profiler-cli thread samples-bottom-up --session <id> --thread t-N --search <fn>
profiler-cli thread markers --session <id> --thread t-N --limit 20
profiler-cli thread markers --session <id> --thread t-N --search "<term>"
profiler-cli marker info --session <id> m-NNNN # one marker in detail--session is mandatory in practice: the "current session" pointer is global and
not sticky, so any other concurrent load silently steals it and your queries start
describing someone else's profile with no error. Never run profiler-cli stop --all;
it kills other agents' sessions too.
You own this session and must stop it. Follow the session cleanup checklist above: stop and verify as soon as the queries are done (end of Step 3), and on every early exit.
If the load fails, that is blocking — fix it and retry. A profile is the artifact the whole analysis rests on, so a failed read is never a caveat to note and move past:
| Failure | Action |
|---|---|
This profile is version N, but this profiler-cli only supports up to version M | The tool is stale. npm install -g @firefox-devtools/profiler-cli@latest, then retry. |
Unable to extract profile URL | The link is a profiler.firefox.com/from-browser/... local-session URL and contains no profile data. Ask for a share.firefox.dev URL produced by Upload Local Profile. |
Please specify one of: --calltree <N>, … | The legacy git-clone profiler-cli is still installed under the same name. Run npm install -g @firefox-devtools/profiler-cli@latest to replace it, then retry. |
Only after upgrading and retrying may you report a profile as unreadable, and then you must say so explicitly and mark the analysis incomplete rather than presenting a conclusion drawn around the hole.
The view can start zoomed. A shared link can open on a selected range. The header
of each command shows View:, and a marker list says (of N in the full range). If
the evidence you need can be outside the range, run
profiler-cli zoom clear --session <id> to see the whole profile.
Check how the profile was captured before drawing regression conclusions. A safe-mode capture forces software compositing and software decode, so it looks pathological on every build and cannot isolate a regression. If the reporter says the symptom also appears in safe mode on the last-good version, their safe-mode profile describes the safe-mode baseline, not the bug: say so and ask for a normal-mode capture.
First, pick the domain pack. The domains/ directory next to this file holds the
threads, marker searches and signatures of one subsystem. If the symptom or the
threads in profile info --all match a pack in the table below, read it now, before
you look at any thread. A matching pack is required, not a hint.
Then start with these threads:
GeckoMain thread of the process that shows the symptom: the content process
for a problem in a page, the parent process for a problem in the browser UI.profile info --all. The busiest thread is
often a result of the problem, not its cause, so do not settle on a cause from CPU
use alone.Run the pack's marker searches where the pack says, and check its signatures in Step 3. Name the searches you ran in the report.
Then follow the evidence. Check another thread only when the profile leads you to
it: a hot stack waits on it, a marker names it, or an IPC message goes to its process.
When a marker or a stack points at a code path, read that code with searchfox-cli
to confirm the cause, instead of stopping at the symptom.
Never stop at the first thread. A symptom often spans more than one thread and
more than one process, so check every thread that the evidence reaches before you
conclude.
The packs:
| Pack | Read it when |
|---|---|
domains/media.md | Media playback: <video>, <audio>, MSE, EME, WebAudio, WebRTC, or Media* and AudioSink threads |
If no pack matches, use only the generic steps.
After collecting raw output, check for these generic patterns, then for the signatures in the domain pack from Step 2:
Jank or LongTask markers on a GeckoMain thread:
thread markers --session <id> --thread <GeckoMain t-N> --min-duration 50profiler-cli zoom push m-N --session <id>), run
thread samples-top-down to see which function holds the thread, then run
profiler-cli zoom pop --session <id>. The zoom stays on until you pop it, and every
later query covers only the zoomed range> 50ms) on GeckoMain:
thread markers --session <id> --thread <GeckoMain t-N> --min-duration 50, and
--category GC for GCMajor, GCSlice, GCMinor and CCSlice markersthread functions --min-self 2, or one
branch with most of the total in thread samples-top-downsearchfox-cli --define '<function>' to see why it
is slowNo later step queries the profile, so stop the daemon now, before writing the report, and verify that it is gone (checklist item 4):
profiler-cli stop --session <id>; profiler-cli session list | grep -F -- "<id>" && echo "STILL RUNNING: <id>"Output the report in this format so callers can consume it directly:
─── Profile Analysis: <url> ──────────────────────────────────────
Threads checked: <every thread from Step 2, e.g. GeckoMain (content), GeckoMain (parent)>
Domain pack: <pack name and the marker searches you ran, or none>
Session: <id> stopped
Findings:
✓ <pattern name>: <what was observed, frequency, thread>
✗ <pattern name>: not detected
…
Key log entries:
[t=Xms] <ThreadName>: <message>
…
Errors:
[t=Xms] <ThreadName>: <NS_ERROR_* or HRESULT>
…
Assessment:
Severity: [Critical / High / Medium / Low / None]
Root cause: [Identified / Suspected / Unknown]
Summary: <2-3 sentences: what the profile shows, what it means for the bug>
Sufficient to start a fix? [Yes / No — reason]
──────────────────────────────────────────────────────────────────Sufficient to start a fix means: the profile alone (possibly combined with existing bug context) gives enough evidence to identify the root cause and start a fix, without needing additional data from the reporter.
/analyze-profile <url>, and drive profiler-cli directly only when this skill
is unavailable or has failed.Profile: inaccessible — <reason>, and the analysis is then marked
incomplete rather than concluded around the gap.profiler-cli directly own their session the same way: follow the
session cleanup checklist.mozilla/firefox-aidev-plugins
0edd81f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.