Skip to main content
Every recorded job produces one Execution Profile. An execution chain is one path through the process tree, from the runner’s root to an action. Today, that action is an outbound connection. The profile ties every chain to the commit, workflow, and job that produced it, and posts back to your PR.

What’s recorded

One Execution Profile, four surfaces. Jibril, an eBPF sensor, runs alongside your workflow and records process and network activity at the kernel. It captures the process paths that led to each outbound connection, with the workflow step attributed where the runner exposed one.

Reading the comment

The screenshot below shows the public comment Garnet posted on garnet-labs/ghaw-garnet-reference#8, with the CI / test job fold open. Screenshot of the public comment Garnet posted on garnet-labs/ghaw-garnet-reference#8, with the CI / test job fold open to show node reaching storage.googleapis[.]com and the runner tree Its count line says 1 of 1 recorded job changed · 1 new on the runner, outside your steps; node newly reaches storage.googleapis[.]com compared with the PR base main. Runner and GitHub traffic is shown in its own tree and never counts toward workload change. Marker counts mirror rendered +/− workload rows, including removals. Capture is marked best-effort. Top to bottom:
  • Verdict and counts — the heading names the state; the count line names recorded jobs that changed.
  • Comparison — one line names the head and comparison source.
  • Capture — the comment states whether capture is best-effort or complete.
  • Jobs — each job has one fold with workload, then runner and GitHub traffic, followed by the job log and public record.
  • Footer — a How to read this fold and Recorded by Garnet · Add it to your CI ↗.

How the comparison is chosen

For each recorded job, the baseline is the union of its last 10 recorded runs on the base branch. If fewer runs exist, Garnet names how many it used. With fewer than two, Garnet uses the PR base alone. If the base has no record, Garnet uses the previous push only when that commit belongs to the pull request. A destination counts as new only when no baseline run reached it. Removals never count: a destination present in every baseline run is shown as a − row with that history, and one present in only some runs is annotated with its count. Runner and GitHub traffic never counts toward workload changes. Workflow step labels place chains but are not displayed. In the #8 example the comment names its comparison: the PR base main.

Attribution

The tree separates your workload from the machinery around it, structurally: Names, domains, and IPs never classify. Only structure does.
(github infra) marks a GitHub Actions infrastructure host. (Cloud IMDS) marks the cloud metadata address. On a − row, (reached in all of the last N main runs) means a host every baseline run reached and this run did not. (K of the last N main runs) marks an unmarked host reached in some baseline runs. Step labels place chains but are not displayed. Loopback is not shown. Every other name and address renders exactly as recorded, in code.
Bold marks a changed job’s row title. + and − mark tree rows: + appears only in this commit, − only in the compared run, and an unmarked row appears in both. Recorded strings are in code. Attribution is structural. See Consume the Runtime Review.

One comment per PR

One sticky comment, updated in place on every push and as each job’s profile lands, pinned to the head commit it records. The action logs any errors it catches as warnings rather than failing your job. The action records the run.

What Garnet sees

GARNET_API_TOKEN is the only repository secret you provision on the token path. With GitHub OIDC (id-token: write, no api_token), there is none. The action also takes github_token, which GitHub supplies as ${{ github.token }} without you storing anything. The action reads no other entry in secrets and never writes to your repository.
  • Metadata, not contents — the fields an Execution Profile is built from are execution chains and the destinations they reached: process names, the recorded step, the working directory, domains, IPs, ports, and protocols. It is not a copy of your source, your environment, or your files. Full field list: Security and permissions.
  • Observe-only — Jibril reads kernel events rather than modifying them; its eBPF programs pass the kernel verifier before they load. The sensor is not only eBPF: it installs with sudo and runs as a root systemd service alongside your job.
  • No pipeline changes — no sidecars, no proxies, no reordering of your steps. On any error the step logs a warning and the job continues.
Attaching eBPF programs requires root, so the install runs under sudo and the sensor runs as root. ubuntu-latest runners include sudo by default.
Store your GARNET_API_TOKEN in CI secrets or a vault. Never commit or log it in plain text.
Permissions, secrets, what runs as root, and what leaves the runner: Security and permissions. Current audit scope, reports, controls, subprocessors, and security documentation live in the Trust Center.

The public run report

Every job’s permalink opens a stable report page for as long as the record stays published:
Public Execution Profile report showing runtime behavior changed: node newly reaches storage.googleapis[.]com

The public report for the same job as the posted #8 comment: compared with the PR base main, node newly reaches storage.googleapis[.]com. Open the public report.

Share it in a PR thread or an incident channel to point teammates at the exact run, or use Share on the report page. The URL fails closed by design:
  • ?profile=<profile-id> is required and exact. Only a published (run, profile) pair resolves; a wrong or missing selector is a 404, never a silent fallback. Use the published report’s permalink. The stable action’s report_url output is a dashboard link, not this public URL.
  • There is no run index. Bare /public/runs/<run-id> — or a ?job= query — is the same 404.
  • A 404 is the same response whatever the reason, so it says nothing about whether a profile exists. What is verified here is the selector contract: a published (run, profile) pair resolves anonymously, and a bare or wrong selector does not. Which records are published, and how publication is granted or withdrawn for a repository, is controlled in the Garnet dashboard, not by this URL shape.
  • The page’s canonical and og:url are the selector URL, and its copy-link button hands back that URL without the utm_ campaign parameters.
The same record is available as JSON at app.garnet.ai/api/public/runs/<run-id>?profile=<profile-id>, same selector rules. It is the fuller of the two published views: the rendered comment leaves out fields the JSON carries, PIDs among them. Machine consumption is a preview; the supported path today ends at this report and the comment.

README badge

A published Execution Profile can back a Garnet Runtime Profiled badge. The badge says the run was recorded, links to the report, and carries no verdict:
Both IDs are in the report’s URL. A badge link without ?profile= is the same 404 as any other bare run URL. Next: Quick start · Consume the Runtime Review (preview)