> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run PR scans with endorctl

> Scan pull requests from the command line, including incremental scans and performance tuning.

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const renderSuccessIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z" fill="currentColor" />
    </svg>;
  const renderFailureIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path fillRule="evenodd" clipRule="evenodd" d="M11.9902 1.98633C12.5208 1.9864 13.0426 2.12743 13.501 2.39453C13.9581 2.66107 14.3372 3.04388 14.5986 3.50391L22.5967 17.5L22.6895 17.6738C22.8921 18.0851 22.9979 18.5387 22.9981 18.999C22.9981 19.5253 22.8605 20.0431 22.5977 20.499C22.3348 20.9549 21.9555 21.3332 21.5 21.5967C21.0445 21.8601 20.5272 21.9994 20.001 22H4.00001C3.47453 22.0031 2.95699 21.868 2.50001 21.6084C2.04032 21.3471 1.65712 20.9684 1.39063 20.5117C1.12431 20.055 0.983643 19.5355 0.982429 19.0068C0.981281 18.4793 1.11967 17.9611 1.38282 17.5039L9.38184 3.50391C9.64344 3.04359 10.023 2.66111 10.4805 2.39453C10.9388 2.12755 11.4598 1.98636 11.9902 1.98633ZM12 16.9004C11.3925 16.9004 10.9004 17.3925 10.9004 18C10.9004 18.6075 11.3925 19.0996 12 19.0996H12.0098C12.6173 19.0996 13.1104 18.6075 13.1104 18C13.1104 17.3925 12.6173 16.9004 12.0098 16.9004H12ZM12 5.90039C11.3925 5.9004 10.9004 6.39249 10.9004 7V13C10.9004 13.6075 11.3925 14.0996 12 14.0996C12.6075 14.0996 13.0996 13.6075 13.0996 13V7C13.0996 6.39249 12.6075 5.90039 12 5.90039Z" fill="currentColor" />
    </svg>;
  const renderPartialSuccessIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path d="M12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1ZM12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3ZM12 5C13.8565 5 15.6374 5.73705 16.9502 7.0498C18.263 8.36256 19 10.1435 19 12C19 13.8565 18.263 15.6374 16.9502 16.9502C15.6374 18.263 13.8565 19 12 19C11.4477 19 11 18.5523 11 18V6C11 5.73478 11.1054 5.4805 11.293 5.29297C11.4805 5.10543 11.7348 5 12 5Z" fill="currentColor" />
    </svg>;
  const renderPendingIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <path d="M10.1 2.182a10 10 0 0 1 3.8 0" />
      <path d="M13.9 21.818a10 10 0 0 1-3.8 0" />
      <path d="M17.609 3.721a10 10 0 0 1 2.69 2.7" />
      <path d="M2.182 13.9a10 10 0 0 1 0-3.8" />
      <path d="M20.279 17.609a10 10 0 0 1-2.7 2.69" />
      <path d="M21.818 10.1a10 10 0 0 1 0 3.8" />
      <path d="M3.721 6.391a10 10 0 0 1 2.7-2.69" />
      <path d="M6.391 20.279a10 10 0 0 1-2.69-2.7" />
    </svg>;
  const renderRunningIcon = () => <svg className="yt-status-running-svg" viewBox="22 22 44 44" width="100%" height="100%" aria-hidden="true">
      <circle className="yt-status-running-circle" cx="44" cy="44" r="20.2" fill="none" stroke="currentColor" strokeWidth="3.6" />
    </svg>;
  const renderSkippedIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="10" />
      <path d="M8 12h8" />
    </svg>;
  const STATUS_ICON_DEFS = [{
    re: /^-status-success-$/i,
    colorClass: 'yt-status-color-success',
    label: 'Success',
    render: renderSuccessIcon
  }, {
    re: /^-status-failure-$/i,
    colorClass: 'yt-status-color-failure',
    label: 'Failure',
    render: renderFailureIcon
  }, {
    re: /^-status-partial-success-$/i,
    colorClass: 'yt-status-color-partial',
    label: 'Partial success',
    render: renderPartialSuccessIcon
  }, {
    re: /^-status-pending-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Pending',
    render: renderPendingIcon
  }, {
    re: /^-status-running-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Running',
    render: renderRunningIcon
  }, {
    re: /^-status-skipped-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Skipped',
    render: renderSkippedIcon
  }];
  const renderStatusBadge = def => <span className={`yt-status-icon ${def.colorClass}`} role="img" aria-label={def.label} title={def.label}>
      {def.render()}
    </span>;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    const statusBadge = STATUS_ICON_DEFS.find(def => def.re.test(text));
    if (statusBadge) return renderStatusBadge(statusBadge);
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  const scrollWrapRef = useRef(null);
  const [isScrollable, setIsScrollable] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  useEffect(() => {
    const wrap = scrollWrapRef.current;
    if (!wrap) return undefined;
    const updateScrollable = () => {
      setIsScrollable(wrap.scrollWidth > wrap.clientWidth);
    };
    updateScrollable();
    const observer = new ResizeObserver(updateScrollable);
    observer.observe(wrap);
    return () => observer.disconnect();
  }, [mounted, data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <div ref={scrollWrapRef} style={{
    overflowX: 'auto',
    margin: '1.75rem 0'
  }} role={isScrollable ? 'region' : undefined} aria-label={isScrollable ? 'Scrollable table' : undefined} tabIndex={isScrollable ? 0 : undefined}>
      <table style={{
    display: 'table',
    width: '100%',
    minWidth: '100%',
    margin: 0
  }}>
        <thead>
          <tr>
            {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
          </tr>
        </thead>
        <tbody>
          {data.map(row => <tr key={rowKey(row)}>
              {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
            </tr>)}
        </tbody>
      </table>
    </div>;
};

You can scan pull requests or merge requests using endorctl for GitHub, GitLab, and Bitbucket. The `--pr` flag runs the scan for the current commit and records the results as PR Runs that do not affect main branch monitoring scans and reports. Endor Labs stores PR and MR scan findings in PR Runs for three weeks, after which they are removed to accommodate new PR scans.

Before you run PR scans, scan your baseline branch at least once so incremental PR scans have a baseline to compare against.

## Scan PRs using endorctl

A PR scan command combines the following flags.

<YamlTable>
  {`
    - Flag: \`--namespace\`
    Required: Yes
    Description: The Endor Labs namespace that stores the scan results.
    - Flag: \`--pr\`
    Required: Yes
    Description: Records the results as a PR Run instead of a monitoring scan.
    - Flag: \`--pr-incremental\`
    Required: No
    Description: Scans only the packages and dependencies that changed relative to the baseline. Recommended for fast PR feedback. See [Perform incremental PR scan](#perform-incremental-pr-scan).
    - Flag: \`--quick-scan\`
    Required: No
    Description: Skips call graph generation, so findings are not annotated with reachability. With \`--pr-incremental\`, it also skips dependency resolution for unaffected languages and packages.
    - Flag: \`--scm-pr-id\`
    Required: No
    Description: The pull request or merge request number. Required when you set \`--enable-pr-comments\`.
    - Flag: \`--enable-pr-comments\`
    Required: No
    Description: Posts new findings as review comments and infers the baseline from the merge target of the PR. Do not set together with \`--pr-baseline\`.
    - Flag: \`--scm-token\`
    Required: No
    Description: The token endorctl uses to authenticate with your SCM to read PR metadata and post comments. You can also set it through \`ENDOR_SCAN_SCM_TOKEN\`.
    `}
</YamlTable>

The following sections explain how these flags work together. To go straight to the ready-to-run command, see [Set up PR scans step by step](#set-up-pr-scans-step-by-step).

## Perform incremental PR scan

An incremental PR scan scans only the parts of the codebase and dependencies that have changed since the last full baseline scan.

* Endor Labs identifies packages and dependencies in the PR and scans only those that changed relative to the baseline.
* If no dependencies changed, the scan is skipped, and Endor Labs reports `No changes found`.
* Incremental PR scans report only findings that involve dependencies the pull request changed and that do not exist in the baseline.
* You can enable incremental PR scans using the `--pr-incremental` flag or the equivalent CI settings. This flag is also available for [SAST incremental scans](/scan/sast#sast-incremental-scans) and [Incremental secret scans](/scan/secrets#incremental-secret-scans).

You need to set a baseline for incremental PR scans so only findings new relative to that branch are reported. For GitHub App or GitLab App scans, or when PR comments are enabled, the baseline is detected automatically. Otherwise, pass `--pr-baseline` when you run the scan. See [Set a default branch](/scan/sca/scanning-strategies#set-a-default-branch) for how the default branch is chosen and used.

<Note>
  **Baseline mismatch in PR scans**

  If a finding is fixed in the baseline by upgrading or downgrading a dependency and a PR still modifies that package, the finding can be reported as new. To mitigate this, rebase the PR with the latest baseline content and re-run the PR check.
</Note>

### Publish findings as PR comments

Endor Labs can post new findings as review comments on the pull request or merge request. Comments are posted according to your action policies. To publish comments:

* Set `--enable-pr-comments` and `--scm-pr-id` so the scan posts comments to the right PR and infers the baseline from its merge target.
* Authenticate with `--scm-token` or the `ENDOR_SCAN_SCM_TOKEN` environment variable.
* Configure an action policy with Branch Type **Pull Request** so violations generate comments.

See [Pull Request comments](/scan/pr-scans/pr-comments) for app-based setup, the required action policy configuration, and comment templates.

### Skip unaffected languages during incremental PR scans

When you combine `--pr-incremental` with `--quick-scan`, Endor Labs inspects the changed files in the pull request before resolving dependencies for each language. If the pull request contains no build file changes for a language, Endor Labs skips dependency resolution for that language entirely. This behavior is enabled by default for all supported languages and ecosystems in endorctl v1.7.1002 and later.

The scan log records each skipped language.

```text theme={null}
Skip Java dependency resolution: No relevant changes detected
```

### Skip unaffected packages during incremental PR scans

When a pull request does change build files, Endor Labs can prune further and resolve dependencies only for the packages those files affect. This reduces incremental PR scan times in large repositories and monorepos.

Endor Labs classifies each changed build file by its scope of impact. The following examples come from JVM projects, and every supported language or ecosystem has an equivalent set of build files:

* A single package, such as `gradle.lockfile`.
* A package and every package under it, such as a parent `pom.xml`, `settings.gradle`, or `gradle.properties`.
* The entire repository, such as `gradle-wrapper.properties`, files under `buildSrc` or `.mvn`, and version catalogs like `gradle/libs.versions.toml`.

Packages that no changed file affects are not resolved again, and their results carry forward from the baseline scan.

Endor Labs enables package-level skipping by default in the following endorctl versions.

<YamlTable>
  {`
    - Language_or_ecosystem: JVM languages that build with Maven or Gradle, including Java, Kotlin, and Scala
    Enabled_by_default_in: v1.7.1046 and later
    - Language_or_ecosystem: JavaScript and TypeScript
    Enabled_by_default_in: v1.7.1063 and later
    - Language_or_ecosystem: Python
    Enabled_by_default_in: v1.7.1080 and later
    - Language_or_ecosystem: Go
    Enabled_by_default_in: v1.7.1081 and later
    - Language_or_ecosystem: .NET
    Enabled_by_default_in: v1.7.1092 and later
    - Language_or_ecosystem: Swift with CocoaPods
    Enabled_by_default_in: v1.7.1103 and later
    - Language_or_ecosystem: Swift with Swift Package Manager
    Enabled_by_default_in: v1.7.1105 and later
    - Language_or_ecosystem: Ruby
    Enabled_by_default_in: v1.7.1128 and later
    - Language_or_ecosystem: PHP
    Enabled_by_default_in: v1.7.1128 and later
    - Language_or_ecosystem: Rust
    Enabled_by_default_in: v1.7.1128 and later
    `}
</YamlTable>

To turn off package-level skipping, set `ENDOR_SCAN_INCREMENTAL_DEP_RES=false` before you run the scan.

### Troubleshoot incremental PR scan performance

If an incremental PR scan takes longer than expected or does not skip dependency resolution, do the following checks:

1. Verify that endorctl is v1.7.1002 or later. Package-level skipping requires a later version, depending on your language or ecosystem. See [Skip unaffected packages during incremental PR scans](#skip-unaffected-packages-during-incremental-pr-scans).
2. Confirm that the scan sets both `--pr-incremental` and `--quick-scan`.
3. Confirm that the scan is a pull request scan. Scans tagged `merge-to-main` resolve all dependencies.
4. Check the scan log for `Detected N changed files`. An unexpectedly large count means the pull request diff could not be computed correctly.
5. Look for skip confirmations in the scan log, such as `Skip dependency resolution for 3/12 maven modules: No relevant changes detected`.
6. Check whether the pull request changes a root or parent build file. These changes affect every module in the repository, so Endor Labs resolves all of them even with package-level skipping turned on.

### Maven dependency resolution on large projects

Maven dependency resolution can take a long time on projects with large or complex dependency trees, when a PR changes a root or parent build file.

To reduce dependency resolution time in PR scans:

* Confirm that `--pr-incremental` and `--quick-scan` are both set. Endor Labs skips dependency resolution for languages and packages that a pull request does not affect.

  See [Skip unaffected packages during incremental PR scans](#skip-unaffected-packages-during-incremental-pr-scans).

* For monorepos with 100 or more `pom.xml` files, set `ENDOR_SCAN_MAVEN_PREPOP_CACHE=true` in GitHub App or other hosted scans to pre-populate the Maven local cache in parallel before Endor Labs scans each module. This setting has no effect in CI-triggered or local `endorctl scan` runs. See [Environment variables that affect scan behavior](/developers-api/cli/environment-variables#environment-variables-that-affect-scan-behavior).

## Set up PR scans step by step

Add the following flags to your scan command in order, and verify each step in your CI pipeline before adding the next.

<Steps>
  <Step title="Start with a PR scan">
    Run a PR scan on the current commit. Endor Labs records the results as a PR Run instead of a monitoring scan.

    ```bash theme={null}
    endorctl scan --namespace <your-namespace> --pr
    ```
  </Step>

  <Step title="Scan only what changed">
    Add `--pr-incremental` and point `--pr-baseline` at the branch the PR merges into. The scan reports only findings that are new relative to the baseline.

    ```bash theme={null}
    endorctl scan --namespace <your-namespace> --pr --pr-incremental --pr-baseline=main
    ```
  </Step>

  <Step title="Speed up the scan">
    Add `--quick-scan` to skip call graph generation and skip dependency resolution for languages and packages the PR does not affect.

    ```bash theme={null}
    endorctl scan --namespace <your-namespace> --pr --pr-incremental --pr-baseline=main --quick-scan
    ```
  </Step>

  <Step title="Post findings as PR comments">
    Add `--enable-pr-comments` with `--scm-pr-id` and an SCM token. Remove `--pr-baseline` because the scan now infers the baseline from the merge target of the PR. This is the recommended command.

    ```bash theme={null}
    export ENDOR_SCAN_SCM_TOKEN=<your-scm-token>
    endorctl scan --namespace <your-namespace> --pr --pr-incremental --quick-scan \
      --scm-pr-id <pr-or-mr-id> --enable-pr-comments
    ```
  </Step>

  <Step title="Block merges on findings">
    The scan reports findings and posts comments, but it does not block merges on its own. Pair the command with an action policy that breaks the build and a required status check on your target branch. See [Block pull requests on findings](/scan/pr-scans/block-pull-requests).
  </Step>

  <Step title="Optional: Point to GitHub Enterprise Server">
    If your organization runs GitHub Enterprise Server, add `--github-api-url` with the API URL of your server.

    ```bash theme={null}
    endorctl scan --namespace <your-namespace> --pr --pr-incremental --quick-scan \
      --scm-pr-id <pr-or-mr-id> --enable-pr-comments --github-api-url=<your-github-api-url>
    ```
  </Step>
</Steps>

### Adjust the command for your setup

* **Run without PR comments**: Stop after step 3. Keep `--pr-baseline` and do not set `--enable-pr-comments` or `--scm-pr-id`.
* **Include reachability analysis**: Drop `--quick-scan` to resolve dependencies fully and generate call graphs. The scan takes longer, but findings include reachability information.
