Static analysis + CI 2026

2026 OpenClaw Frontend on a Remote Mac:
One Gate Summary from ESLint JSON + Stylelint JSON → PR-Readable Comments

April 27, 2026 Frontend teams (CI + AI summaries) 8 min read

Audience: frontend teams that want static checks inside CI and the same signals fed to an AI release digest—without maintaining two parallel narratives (terminal ESLint and a separate Stylelint wall). Keywords: OpenClaw, ESLint JSON, Stylelint, remote Mac, PR summary. This HowTo assumes Node 22+ on workers and an OpenClaw-style gateway that sits next to your Git host: it ingests machine-readable reports, applies policy, and returns one Markdown block reviewers can read inline. Pair the basics with ESLint and Stylelint JSON → fix-branch steps and token-scoped automation for E2E summaries when you wire secrets.

00 HowTo: reproducible checklist

Run these steps on every worker—including your leased remote Mac—so local repro matches CI and the gateway never guesses paths.

  1. Install dependencies with the same Node 22+ major as CI; commit package.json engines if you enforce it.
  2. Emit ESLint JSON with eslint . -f json -o .openclaw/reports/eslint.json (flat config friendly) and Stylelint with stylelint "**/*.{css,scss}" --formatter json -o .openclaw/reports/stylelint.json.
  3. CI uploads both files (or posts them) to the OpenClaw gateway together with GIT_SHA, PR_NUMBER, and optional BASE_REF.
  4. The gateway runs a merge script (see below), writes pr_lint_gate_summary.md, sets exit status from thresholds, and calls the Git provider to upsert a PR comment.
  5. Downstream AI jobs read only the merged summary path so prompts stay small and deterministic.

For ordering inside a longer pipeline, keep lint JSON generation before heavy browser work—see Playwright shard reports on remote Mac for a compatible stage list.

01 Unifying local and CI output formats

Divergence between a developer laptop and CI is the main reason gateways double-count or miss issues. Fix it by contract, not by convention.

Path normalization: ESLint often prints absolute paths per file object; Stylelint uses source strings that may also be absolute. Strip the repository root on the gateway so grouping keys are repo-relative. That keeps PR summary lines stable when the same clone path differs between hosts.

Formatter lock-in: never rely on default TTY format in CI. Always pass explicit -f json / --formatter json and write to -o files so stdout stays clean for other tools. On Apple Silicon runners, match npm_config_arch only when native addons affect lint plugins; the JSON shape should still be identical.

Snippet — package scripts (Node 22+):

{
  "engines": { "node": ">=22" },
  "scripts": {
    "lint:eslint:json": "mkdir -p .openclaw/reports && eslint . -f json -o .openclaw/reports/eslint.json",
    "lint:stylelint:json": "mkdir -p .openclaw/reports && stylelint \"**/*.{css,scss}\" --formatter json -o .openclaw/reports/stylelint.json",
    "lint:json:all": "npm run lint:eslint:json && npm run lint:stylelint:json"
  }
}

02 Gateway-side aggregation and thresholds

The gateway’s job is not to rerun linters—it is to merge ESLint JSON (per-file messages with severity) and Stylelint arrays (warnings, errored) into one lint_gate/v1 object: totals, top rules, worst files, and tool attribution. That object drives both human Markdown and machine consumers (Slack, LLM).

Thresholds: treat ESLint severity 2 and Stylelint warnings on errored: true runs as hard errors. Maintain a separate warning budget YAML checked into the repo, for example max_stylelint_warnings: 12 on release branches while feature branches stay warn-only. The gateway compares parsed counts to those budgets and sets HTTP status or CI exit codes accordingly.

Snippet — merge skeleton (Node 22 ESM):

// tools/merge-lint-gate.mjs — run on the OpenClaw gateway after reports land
import { readFile, writeFile } from "node:fs/promises";

const root = process.env.REPO_ROOT ?? "";
const strip = (p) => (p && p.startsWith(root) ? p.slice(root.length + 1) : p);

const eslint = JSON.parse(await readFile(".openclaw/reports/eslint.json", "utf8"));
const style = JSON.parse(await readFile(".openclaw/reports/stylelint.json", "utf8"));

const rows = [];
for (const f of Array.isArray(eslint) ? eslint : []) {
  for (const m of f.messages ?? []) {
    rows.push({ tool: "eslint", file: strip(f.filePath), rule: m.ruleId ?? "syntax", sev: m.severity });
  }
}
for (const f of Array.isArray(style) ? style : []) {
  for (const w of f.warnings ?? []) {
    rows.push({ tool: "stylelint", file: strip(f.source), rule: w.rule ?? "unknown", sev: w.severity });
  }
}

const gate = {
  version: 1,
  errors: rows.filter((r) => r.sev === 2 || r.sev === "error").length,
  warnings: rows.filter((r) => r.sev === 1 || r.sev === "warning").length,
  topRules: /* group rows by rule, take top N */ [],
};
await writeFile(".openclaw/reports/lint_gate.json", JSON.stringify(gate, null, 2));

Fill topRules with your preferred reducer; keep the emitted JSON schema versioned so OpenClaw prompts can reference lint_gate/v1 without surprise field renames.

03 Failure retries and truncation strategy

Two different failure classes need two policies. Transport and provider limits (429, 5xx, TLS blips) should retry with exponential backoff and jitter—especially when the gateway batches multiple PRs from the same remote Mac IP. Lint failures should not retry blindly; instead re-run only when you detect incomplete JSON (zero files but non-zero exit) after a crash.

Truncation: cap rendered findings per rule (for example five files) and cap total lines in the Markdown PR summary (for example 120 lines). Append a single line: “Truncated: N additional findings omitted—see CI artifact eslint.json / stylelint.json.” Those artifact names are safe; avoid embedding long base64 blobs in comments.

Snippet — bounded retry shell wrapper:

#!/usr/bin/env bash
set -euo pipefail
attempt=0 max=4
until node tools/post-pr-lint-summary.mjs; do
  code=$?
  attempt=$((attempt + 1))
  if [[ "$code" -eq 77 ]] && [[ "$attempt" -lt "$max" ]]; then
    sleep $((2 ** attempt + RANDOM % 3))
    continue
  fi
  exit "$code"
done

Reserve exit 77 (or similar) inside post-pr-lint-summary.mjs for “retryable provider error” so you never confuse a throttled API with a broken lint tree.

04 Git provider comment API: design notes (no login-gated private URLs)

Most teams use either the REST “issue comments on a pull request” style endpoint or a review-comment API that anchors to a diff. Both patterns work with OpenClaw as long as the gateway holds a scoped token (fine-grained PAT or GitHub App installation token, GitLab job token, etc.).

Idempotency: prepend a hidden HTML comment marker such as <!-- openclaw-lint-gate:deadbeef --> derived from repository, PR number, and lint input hashes. On each run, search previous bot comments for that marker; update in place instead of spamming ten duplicates when CI requeues.

What not to paste: never place private preview URLs that require org login into the public PR comment—reviewers without access get dead links and search engines may leak titles. Prefer neutral wording: “See CI job #12345 artifacts” or link only to your public docs. For deeper detail, mirror anonymized snippets inside the comment itself (truncated) or attach machine JSON as build artifacts behind normal CI permissions.

Minimal POST shape (illustrative): HTTP POST /repos/{owner}/{repo}/issues/{pr_number}/comments with JSON body { "body": "<markdown digest>" } and Authorization: Bearer …. Map GitLab or Bitbucket to their equivalent resource; the OpenClaw gateway only needs a thin adapter per host.

Takeaway

When ESLint JSON and Stylelint share one path contract and one merge step on the OpenClaw gateway, your PR summary becomes a single gate narrative: counts, top rules, bounded file lists, and a clear pass/fail line—ideal for both human reviewers and downstream CI + AI summarizers on a remote Mac worker.

More on the site: Home, Help center, and the full blog index.

Apple Silicon CI host

Run the Same OpenClaw Lint Gate on a Dedicated Remote Mac

If you want deterministic Node 22+ workers next to Safari and WebKit smoke tests, rent an Apple Silicon remote Mac for your gateway and CI. Open buy.html to compare plans and complete checkout without creating an account—ideal for teams standardizing OpenClaw, ESLint JSON, and Stylelint in one pipeline.

Single lint gate digest PR comment automation Apple Silicon
Rent M4 — No Login