run macroscope reviews on PRs made on tangled
Files
README.md

Tangled → Macroscope #

A small Go server for one Tangled repository. It receives signed PR webhooks, checks out the submitted patch, runs the Macroscope CLI, and posts a PR comment with findings linked to exact commits and lines. It does not approve or merge PRs.

Uses the Go standard library only. Requires Go 1.23+, Git, and Linux or macOS. Targets Tangled's current org.tangled.* APIs and repository-DID knots, not the older sh.tangled.* API. The repository must be readable over HTTPS.

Set up #

  1. Create a dedicated AT Protocol bot account and generate an app password through its PDS/account provider. The bot must be allowed to comment under the repository's contribution policy. Public commenting usually needs no collaborator grant.

  2. Install the Macroscope CLI. Run macroscope setup as the same OS user that will run this server, and select a workspace with available review credits. macroscope me checks that session. On Linux without a keyring, the CLI documents file credential storage; use the same storage setting when running the server.

  3. Copy and edit the configuration:

    cp .env.example .env
    chmod 600 .env
    

    Set the bot's PDS URL, handle and app password. Set the repository's web URL, knot URL and repository DID. Find the latter two in the repository's permanent HTTPS clone URL: https://<knot-host>/did:plc:.... Set WEBHOOK_SECRET to a random value, e.g. generated with openssl rand -hex 32.

  4. Build and run:

    go build -o tangled-macroscope .
    set -a
    . ./.env
    set +a
    ./tangled-macroscope
    
  5. Put the server behind a public HTTPS reverse proxy, forwarding /webhook to 127.0.0.1:8080. Keep the process running with your usual service manager. Tangled refuses private webhook destinations. For example, Caddy can use:

    reviews.example.com {
        reverse_proxy /webhook 127.0.0.1:8080
    }
    
  6. In Tangled, select a spindle under Repository → Settings → Pipelines. Under Settings → Hooks, add https://reviews.example.com/webhook, enter the same webhook secret, and select PR opened, PR resubmitted, and optionally PR reopened. Then open or resubmit a PR.

The Macroscope session is separate from the bot's app password. This server doesn't install Macroscope, open a browser, or update its CLI automatically.

Docker #

The image includes Git, ripgrep, and a pinned, checksum-verified Macroscope CLI. It runs as UID/GID 10001:10001. To build locally (including Go race tests and go vet):

docker build -t tangled-scope:local .

Once CI has published an image, sign in to Macroscope once using a persistent volume. Follow the sign-in URL shown by the wizard and select your workspace:

docker run --rm -it \
  -v tangled-scope-macroscope:/home/scope/.macroscope \
  --entrypoint macroscope \
  docker.io/kennethnym/tangled-scope:latest setup

Configure .env using the setup instructions above, then start the server:

docker run -d --name tangled-scope --restart unless-stopped \
  --env-file .env \
  -e LISTEN_ADDR=0.0.0.0:8080 -e DATA_DIR=/data \
  -p 127.0.0.1:8080:8080 \
  -v tangled-scope-data:/data \
  -v tangled-scope-macroscope:/home/scope/.macroscope \
  docker.io/kennethnym/tangled-scope:latest

Use tangled-scope:local in both commands to run your local build. Published images are also available as atcr.io/kenneth.nym.sh/tangled-scope:latest. The command overrides the host-oriented listen/data defaults in .env.example. Keep both volumes across container replacements. Macroscope uses file credential storage in its volume; protect that volume like a password. Bind-mounted directories must be writable by UID 10001. Keep the HTTPS reverse proxy and Tangled webhook setup from the instructions above.

For CLI updates, update the version and both checksums in Dockerfile, rebuild, and replace the container while retaining the volumes.

Publishing from Tangled CI #

In Settings → Pipelines, select a spindle supporting the microvm engine, the nixos image, and Docker. Add these four repository Secrets (Tangled injects them as environment variables):

Secret Value
DOCKERHUB_USERNAME kennethnym, or a user with push access to that namespace
DOCKERHUB_TOKEN Docker Hub personal access token with Read & Write permission
ATCR_USERNAME kenneth.nym.sh (the full AT Protocol handle)
ATCR_APP_PASSWORD An app password issued by that account's PDS/provider

Create the kennethnym/tangled-scope repository on Docker Hub. ATCR accepts Docker login with an AT Protocol app password, and uses the full account handle as its image namespace. The registry credentials are CI-only; they are separate from the runtime bot and Macroscope credentials.

.tangled/workflows/publish.yml runs on pushes to main and manual runs against main. It builds/tests once, then pushes latest and sha-<full-commit-sha> to docker.io/kennethnym/tangled-scope and atcr.io/$ATCR_USERNAME/tangled-scope. It publishes the runner's native architecture (normally linux/amd64). After adding secrets, retry the pipeline or run it manually on main. See Tangled's secrets documentation.

What happens #

  • Verifies X-Tangled-Signature-256 over the raw body and checks the configured repository. Saves the job to disk before returning 202; one worker runs reviews outside the HTTP request.
  • Resolves the PR using its original author, creation time, and target branch. Tangled currently sends number: 0. The server reads paginated knot records and refuses ambiguous matches instead of guessing.
  • Selects the patch for the webhook's round count, checks its head SHA when supplied, and fetches its fixed base/head commits from the configured knot. Both SHA-1 and SHA-256 repositories are supported. It never clones a URL supplied by a webhook or runs repository tests/hooks.
  • Runs macroscope codereview <checkout> --base <base-sha> --raw --isolate=false in a temporary checkout. Requires exit code zero, a session ID, a review ID, and issue_status=completed. Partial/unknown coverage is stated in the comment.
  • Posts an org.tangled.feed.comment on the PR using a PDS-issued service token addressed to the repository DID. Publishes an authorship attestation on the bot's PDS. Links use /blob/<sha>/<path>#L<line>; deleted files link at the base.
  • Caches completed reviews before posting. An existing comment's bot identity, PR subject and per-patch marker prevent duplicate comments on webhook retries. It checks whether the PR is still open before posting.

Operations #

GET /healthz returns queue counts (pending, running, failed, done). A 200 means the server can read its state; inspect failed to detect job failures. Logs contain job IDs and errors. The queue response means accepted, not that the review has completed.

State lives in DATA_DIR, with owner-only files:

  • jobs/*.json: webhook payload, status and last error.
  • reviews/*.json: completed reviews, reused when posting is retried.
  • reviews/*.log: bounded CLI output; .git.log files contain Git failures.

Retry: fix the reported cause, then select Retry on the original Tangled webhook delivery. Failed jobs are requeued. Completed reviews are reused; a failed/incomplete CLI run is rerun and may incur another charge. There are no automatic retries of failed jobs. Pending jobs survive restarts. Jobs interrupted while running are marked failed at startup and require an explicit redelivery.

Keep DATA_DIR across restarts and run only one process per data directory (enforced with a file lock). Files are retained until you remove them. A retry after a comment response is lost checks the knot before attempting another post. The remote API has no idempotency key, so this is reconciliation rather than a transactional exactly-once guarantee.

The default timeout is 45 minutes. Change REVIEW_TIMEOUT for large repositories. Macroscope may run silently for many minutes. CLI update-required errors must be resolved by updating the installed CLI, then retrying the delivery.

BOT_*, WEBHOOK_*, and TANGLED_* environment variables are removed from child processes. Git hooks and user/system Git configuration are disabled for the temporary checkout. Macroscope still needs its own OS-user credentials.

Tests and compatibility #

go test -race ./...
go vet ./...

Tests use mock PDS/knot APIs and a real Git repository served through git http-backend in both SHA-1 and SHA-256 formats, with a fixture executable replacing paid Macroscope calls. They cover signatures, pagination, frozen rounds, session refresh, comment posting, code links, failures, retry caching and duplicate delivery handling.

No live bot comment or paid Macroscope review has been run during development. The public knot's repository-manifest read was also checked against Tangled's own repository. The first deployment needs a test PR to verify the selected PDS, knot and Macroscope account together.

API references: Tangled webhooks, Tangled records, knot writes and service authentication, comment records, Macroscope CLI.