- Go 97%
- Docker 3%
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 #
-
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.
-
Install the Macroscope CLI. Run
macroscope setupas the same OS user that will run this server, and select a workspace with available review credits.macroscope mechecks that session. On Linux without a keyring, the CLI documents file credential storage; use the same storage setting when running the server. -
Copy and edit the configuration:
cp .env.example .env chmod 600 .envSet 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:.... SetWEBHOOK_SECRETto a random value, e.g. generated withopenssl rand -hex 32. -
Build and run:
go build -o tangled-macroscope . set -a . ./.env set +a ./tangled-macroscope -
Put the server behind a public HTTPS reverse proxy, forwarding
/webhookto127.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 } -
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-256over the raw body and checks the configured repository. Saves the job to disk before returning202; 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=falsein a temporary checkout. Requires exit code zero, a session ID, a review ID, andissue_status=completed. Partial/unknown coverage is stated in the comment. - Posts an
org.tangled.feed.commenton 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.logfiles 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.