Organisationsahrefsmonorobot624674 (pr-comms-sync)(lint-fmt)

(lint-fmt)

Link Copied
Code Copied

Logs

2026-04-16 16:38.58: New job: test ahrefs/monorobot https://github.com/ahrefs/monorobot.git#refs/heads/pr-comms-sync (624674d8bfd9120bb06964908985ba61b6e7201d) (linux-x86_64:(lint-fmt))
Base: ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c
ocamlformat version: version 0.26.2 (from opam)


To reproduce locally:


git clone --recursive "https://github.com/ahrefs/monorobot.git" -b "pr-comms-sync" && cd "monorobot" && git reset --hard 624674d8
cat > Dockerfile <<'END-OF-DOCKERFILE'
FROM ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c
USER 1000:1000
RUN cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -u
RUN opam depext -i dune
WORKDIR /src
RUN opam depext -i ocamlformat=0.26.2
COPY --chown=1000:1000 . /src/
RUN opam exec -- dune build @fmt --ignore-promoted-rules || (echo "dune build @fmt failed"; exit 2)


END-OF-DOCKERFILE
docker build .
END-REPRO-BLOCK


2026-04-16 16:38.58: Using cache hint "ahrefs/monorobot-ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c-debian-13-4.08_opam-2.5-ocamlformat-5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41"
2026-04-16 16:38.58: Using OBuilder spec:
((from ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c)
(user (uid 1000) (gid 1000))
(run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -u"))
(run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "opam depext -i dune"))
(workdir /src)
(run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "opam depext -i ocamlformat=0.26.2"))
(copy (src .) (dst /src/))
(run (shell "opam exec -- dune build @fmt --ignore-promoted-rules || (echo \"dune build @fmt failed\"; exit 2)"))
)


2026-04-16 16:38.58: Waiting for resource in pool OCluster
2026-04-16 19:19.49: Waiting for worker…
2026-04-16 19:25.17: Got resource from pool OCluster
Building on phoebe.caelum.ci.dev
All commits already cached
HEAD is now at 624674d add bidirectional logic


(from ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c)
Unable to find image 'ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c' locally
docker.io/ocaml/opam@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c: Pulling from ocaml/opam
a7730063fcfe: Pulling fs layer
1a27fd2181de: Pulling fs layer
c29591c91388: Pulling fs layer
356a64daa753: Pulling fs layer
ac02f8f2062b: Pulling fs layer
aaa2f390e4c8: Pulling fs layer
b668811757f6: Pulling fs layer
32fd4e1a774f: Pulling fs layer
b1d486fb8fdf: Pulling fs layer
cf8a2024f299: Pulling fs layer
8c4a5dac67c3: Pulling fs layer
6adfe24d7b40: Pulling fs layer
dccd6e3c0589: Pulling fs layer
2ee4bcb55cd4: Pulling fs layer
2dc3b58478d7: Pulling fs layer
daf15e5c44c6: Pulling fs layer
564e978a8088: Pulling fs layer
25a652a8c456: Pulling fs layer
b3bfea7bff3f: Pulling fs layer
a00f2937f570: Pulling fs layer
78bd1737ebff: Pulling fs layer
4f4fb700ef54: Pulling fs layer
4291a055edd7: Pulling fs layer
b668811757f6: Waiting
3c2b2836d59a: Pulling fs layer
32fd4e1a774f: Waiting
b1d486fb8fdf: Waiting
7b3e96544a52: Pulling fs layer
cf8a2024f299: Waiting
c72503effb14: Pulling fs layer
8c4a5dac67c3: Waiting
6adfe24d7b40: Waiting
bca08df11a10: Pulling fs layer
dccd6e3c0589: Waiting
2ee4bcb55cd4: Waiting
2ab0829b2daf: Pulling fs layer
2dc3b58478d7: Waiting
daf15e5c44c6: Waiting
1d246d4da211: Pulling fs layer
564e978a8088: Waiting
a09cb4870027: Pulling fs layer
25a652a8c456: Waiting
b3bfea7bff3f: Waiting
2ce07b4fe7c0: Pulling fs layer
a00f2937f570: Waiting
6b96f28d505e: Pulling fs layer
b6ad36bba9bf: Pulling fs layer
56d62791a0f9: Pulling fs layer
4d60780055d1: Pulling fs layer
43f57a7c44cb: Pulling fs layer
f21057dc4e85: Pulling fs layer
e0d2ca1300ed: Pulling fs layer
356a64daa753: Waiting
ac02f8f2062b: Waiting
9da2f7598f8e: Pulling fs layer
aaa2f390e4c8: Waiting
cb569bb28593: Pulling fs layer
78bd1737ebff: Waiting
4f4fb700ef54: Waiting
b32c9abef0bb: Pulling fs layer
4291a055edd7: Waiting
3c2b2836d59a: Waiting
7b3e96544a52: Waiting
dd7da3e42740: Pulling fs layer
c72503effb14: Waiting
2ce07b4fe7c0: Waiting
bca08df11a10: Waiting
06223904e4d3: Pulling fs layer
6b96f28d505e: Waiting
2ab0829b2daf: Waiting
b6ad36bba9bf: Waiting
1d246d4da211: Waiting
b18a38618cf8: Pulling fs layer
a09cb4870027: Waiting
4d60780055d1: Waiting
56d62791a0f9: Waiting
43f57a7c44cb: Waiting
b32c9abef0bb: Waiting
cb569bb28593: Waiting
f21057dc4e85: Waiting
dd7da3e42740: Waiting
b18a38618cf8: Waiting
06223904e4d3: Waiting
e0d2ca1300ed: Waiting
9da2f7598f8e: Waiting
c29591c91388: Verifying Checksum
c29591c91388: Download complete
1a27fd2181de: Verifying Checksum
1a27fd2181de: Download complete
ac02f8f2062b: Verifying Checksum
ac02f8f2062b: Download complete
a7730063fcfe: Verifying Checksum
a7730063fcfe: Download complete
356a64daa753: Verifying Checksum
356a64daa753: Download complete
32fd4e1a774f: Verifying Checksum
32fd4e1a774f: Download complete
b668811757f6: Verifying Checksum
b668811757f6: Download complete
b1d486fb8fdf: Verifying Checksum
b1d486fb8fdf: Download complete
cf8a2024f299: Verifying Checksum
cf8a2024f299: Download complete
8c4a5dac67c3: Verifying Checksum
8c4a5dac67c3: Download complete
6adfe24d7b40: Verifying Checksum
6adfe24d7b40: Download complete
dccd6e3c0589: Download complete
2ee4bcb55cd4: Verifying Checksum
2ee4bcb55cd4: Download complete
2dc3b58478d7: Download complete
564e978a8088: Verifying Checksum
564e978a8088: Download complete
25a652a8c456: Download complete
b3bfea7bff3f: Verifying Checksum
b3bfea7bff3f: Download complete
a00f2937f570: Download complete
aaa2f390e4c8: Download complete
78bd1737ebff: Download complete
4f4fb700ef54: Download complete
4291a055edd7: Download complete
3c2b2836d59a: Download complete
7b3e96544a52: Verifying Checksum
7b3e96544a52: Download complete
c72503effb14: Verifying Checksum
c72503effb14: Download complete
bca08df11a10: Verifying Checksum
bca08df11a10: Download complete
2ab0829b2daf: Verifying Checksum
2ab0829b2daf: Download complete
1d246d4da211: Download complete
a09cb4870027: Download complete
2ce07b4fe7c0: Verifying Checksum
2ce07b4fe7c0: Download complete
6b96f28d505e: Verifying Checksum
6b96f28d505e: Download complete
b6ad36bba9bf: Verifying Checksum
b6ad36bba9bf: Download complete
56d62791a0f9: Verifying Checksum
56d62791a0f9: Download complete
43f57a7c44cb: Verifying Checksum
43f57a7c44cb: Download complete
e0d2ca1300ed: Verifying Checksum
e0d2ca1300ed: Download complete
9da2f7598f8e: Verifying Checksum
9da2f7598f8e: Download complete
cb569bb28593: Verifying Checksum
cb569bb28593: Download complete
a7730063fcfe: Pull complete
1a27fd2181de: Pull complete
c29591c91388: Pull complete
356a64daa753: Pull complete
ac02f8f2062b: Pull complete
b32c9abef0bb: Verifying Checksum
b32c9abef0bb: Download complete
dd7da3e42740: Download complete
4d60780055d1: Verifying Checksum
4d60780055d1: Download complete
06223904e4d3: Verifying Checksum
06223904e4d3: Download complete
b18a38618cf8: Verifying Checksum
b18a38618cf8: Download complete
f21057dc4e85: Verifying Checksum
f21057dc4e85: Download complete
aaa2f390e4c8: Pull complete
b668811757f6: Pull complete
32fd4e1a774f: Pull complete
b1d486fb8fdf: Pull complete
cf8a2024f299: Pull complete
8c4a5dac67c3: Pull complete
6adfe24d7b40: Pull complete
dccd6e3c0589: Pull complete
2ee4bcb55cd4: Pull complete
2dc3b58478d7: Pull complete
daf15e5c44c6: Pull complete
564e978a8088: Pull complete
25a652a8c456: Pull complete
b3bfea7bff3f: Pull complete
a00f2937f570: Pull complete
78bd1737ebff: Pull complete
4f4fb700ef54: Pull complete
4291a055edd7: Pull complete
3c2b2836d59a: Pull complete
7b3e96544a52: Pull complete
c72503effb14: Pull complete
bca08df11a10: Pull complete
2ab0829b2daf: Pull complete
1d246d4da211: Pull complete
a09cb4870027: Pull complete
2ce07b4fe7c0: Pull complete
6b96f28d505e: Pull complete
b6ad36bba9bf: Pull complete
56d62791a0f9: Pull complete
4d60780055d1: Pull complete
43f57a7c44cb: Pull complete
f21057dc4e85: Pull complete
e0d2ca1300ed: Pull complete
9da2f7598f8e: Pull complete
cb569bb28593: Pull complete
b32c9abef0bb: Pull complete
dd7da3e42740: Pull complete
06223904e4d3: Pull complete
b18a38618cf8: Pull complete
Digest: sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c
Status: Downloaded newer image for ocaml/opam@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c
2026-04-16 19:25.17 ---> using "228e5ba11f0bea08c69266bb98d12a75785691261b07b0808652a307a04e14b2" from cache


/: (user (uid 1000) (gid 1000))


/: (run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -u"))
From https://github.com/ocaml/opam-repository
* branch                  master     -> FETCH_HEAD
42844088d7..ab54f30430  master     -> origin/master
5f7bb1a6e6 Merge pull request #29704 from shonfeder/release-dune-3.22.2


<><> Updating package repositories ><><><><><><><><><><><><><><><><><><><><><><>
[default] Initialised
default (at git+file:///home/opam/opam-repository):
[INFO] opam 2.1 and 2.2 include many performance and security improvements over 2.0; please consider upgrading (https://opam.ocaml.org/doc/Install.html)


Everything as up-to-date as possible (run with --verbose to show unavailable upgrades).
However, you may "opam upgrade" these packages explicitly, which will ask permission to downgrade or uninstall the conflicting packages.
Nothing to do.
# Run eval $(opam env) to update the current shell environment
2026-04-16 19:25.17 ---> using "258cae844a72eb8aff9e0ff154fcd3221fc099e2496be43da4df2e66c163b3fe" from cache


/: (run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "opam depext -i dune"))
# Detecting depexts using vars: arch=x86_64, os=linux, os-distribution=debian, os-family=debian
# No extra OS packages requirements found.
# All required OS packages found.
# Now letting opam install the packages
The following actions will be performed:
- install dune 3.22.2


<><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
[dune.3.22.2] found in cache


<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
-> installed dune.3.22.2
Done.
# Run eval $(opam env) to update the current shell environment
2026-04-16 19:25.17 ---> using "effbcae8fcd9a93cf66de0f212437b65e55b1a8be7f39c4a9a3ce354b6ec7445" from cache


/: (workdir /src)


/src: (run (cache (opam-archives (target /home/opam/.opam/download-cache)))
(network host)
(shell "opam depext -i ocamlformat=0.26.2"))
# Detecting depexts using vars: arch=x86_64, os=linux, os-distribution=debian, os-family=debian
# No extra OS packages requirements found.
# All required OS packages found.
# Now letting opam install the packages
The following actions will be performed:
- install dune-build-info   3.22.2   [required by ocamlformat-lib]
- install sexplib0          v0.14.0  [required by base]
- install cmdliner          1.3.0    [required by ocamlformat]
- install ocamlbuild        0.16.1   [required by fpath, astring, uuseg]
- install ocaml-version     4.0.4    [required by ocamlformat-lib]
- install either            1.0.0    [required by ocamlformat-lib]
- install menhirLib         20260209 [required by ocamlformat-lib]
- install csexp             1.5.2    [required by ocamlformat-lib]
- install menhirSdk         20260209 [required by ocamlformat-lib]
- install menhirGLR         20260209 [required by menhir]
- install result            1.5      [required by ocamlformat-lib]
- install camlp-streams     5.0.1    [required by ocamlformat-lib]
- install seq               base     [required by re]
- install fix               20250919 [required by ocamlformat-lib]
- install ocamlfind         1.9.8    [required by ocp-indent, astring, fpath, uuseg]
- install menhirCST         20260209 [required by menhir]
- install dune-configurator 3.22.2   [required by base]
- install re                1.11.0   [required by ocamlformat]
- install topkg             1.1.1    [required by fpath, astring, uuseg]
- install ocp-indent        1.9.0    [required by ocamlformat-lib]
- install menhir            20260209 [required by ocamlformat-lib]
- install base              v0.14.3  [required by ocamlformat-lib]
- install uutf              1.0.4    [required by ocamlformat-lib]
- install astring           0.8.5    [required by ocamlformat-lib]
- install stdio             v0.14.0  [required by ocamlformat-lib]
- install uucp              15.0.0   [required by uuseg]
- install fpath             0.7.3    [required by ocamlformat-lib]
- install uuseg             15.0.0   [required by ocamlformat-lib]
- install ocamlformat-lib   0.26.2   [required by ocamlformat]
- install ocamlformat       0.26.2
===== 30 to install =====


<><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
[astring.0.8.5] found in cache
[base.v0.14.3] found in cache
[camlp-streams.5.0.1] found in cache
[cmdliner.1.3.0] found in cache
[csexp.1.5.2] found in cache
[dune-build-info.3.22.2] found in cache
[dune-configurator.3.22.2] found in cache
[either.1.0.0] found in cache
[fix.20250919] found in cache
[fpath.0.7.3] found in cache
[menhir.20260209] found in cache
[menhirCST.20260209] found in cache
[menhirGLR.20260209] found in cache
[menhirLib.20260209] found in cache
[menhirSdk.20260209] found in cache
[ocaml-version.4.0.4] found in cache
[ocamlbuild.0.16.1] found in cache
[ocamlfind.1.9.8] found in cache
[ocamlformat.0.26.2] found in cache
[ocamlformat-lib.0.26.2] found in cache
[ocp-indent.1.9.0] found in cache
[re.1.11.0] found in cache
[result.1.5] found in cache
[sexplib0.v0.14.0] found in cache
[stdio.v0.14.0] found in cache
[topkg.1.1.1] found in cache
[uucp.15.0.0] found in cache
[uuseg.15.0.0] found in cache
[uutf.1.0.4] found in cache


<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
-> installed seq.base
-> installed camlp-streams.5.0.1
-> installed csexp.1.5.2
-> installed dune-build-info.3.22.2
-> installed either.1.0.0
-> installed fix.20250919
-> installed cmdliner.1.3.0
-> installed menhirCST.20260209
-> installed menhirGLR.20260209
-> installed menhirLib.20260209
-> installed menhirSdk.20260209
-> installed ocaml-version.4.0.4
-> installed re.1.11.0
-> installed result.1.5
-> installed sexplib0.v0.14.0
-> installed dune-configurator.3.22.2
-> installed ocamlfind.1.9.8
-> installed ocp-indent.1.9.0
-> installed ocamlbuild.0.16.1
-> installed base.v0.14.3
-> installed menhir.20260209
-> installed topkg.1.1.1
-> installed stdio.v0.14.0
-> installed uutf.1.0.4
-> installed astring.0.8.5
-> installed fpath.0.7.3
-> installed uucp.15.0.0
-> installed uuseg.15.0.0
-> installed ocamlformat-lib.0.26.2
-> installed ocamlformat.0.26.2
Done.


<><> ocp-indent.1.9.0 installed successfully ><><><><><><><><><><><><><><><><><>
=> This package requires additional configuration for use in editors. Install package 'user-setup', or manually:


* for Emacs, add these lines to ~/.emacs:
(add-to-list 'load-path "/home/opam/.opam/4.08/share/emacs/site-lisp")
(require 'ocp-indent)


* for Vim, add this line to ~/.vimrc:
set rtp^="/home/opam/.opam/4.08/share/ocp-indent/vim"
# Run eval $(opam env) to update the current shell environment
2026-04-16 19:25.17 ---> using "215d430d1a06e303abbdfd6e5712a0b639b5259e8fbb5bd00fa39461e45689d5" from cache


/src: (copy (src .) (dst /src/))
2026-04-16 19:25.18 ---> saved as "d6bfb22e303ee1070024c1adaf9631b827751ae267a835d9738a9034d22b0afb"


/src: (run (shell "opam exec -- dune build @fmt --ignore-promoted-rules || (echo \"dune build @fmt failed\"; exit 2)"))
File "lib/action.ml", line 1, characters 0-0:
diff --git a/_build/default/lib/action.ml b/_build/default/lib/.formatted/action.ml
index c8671fa..a27f796 100644
--- a/_build/default/lib/action.ml
+++ b/_build/default/lib/.formatted/action.ml
@@ -704,129 +704,127 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) (Buildkite_api :
in
let%lwt () =
send_slack_warning
-            (Printf.sprintf
-               "This thread is no longer synced with GitHub. Please add your comment directly: %s" pr_url)
+            (Printf.sprintf "This thread is no longer synced with GitHub. Please add your comment directly: %s" pr_url)
in
Lwt.return "warning: thread expired"
| Some (repo_url, pr_url) ->
-        (match parse_pr_url pr_url with
-        | None ->
-          log#warn "failed to parse PR URL: %s" pr_url;
-          Lwt.return "error: failed to parse PR URL"
-        | Some (_repo_url, owner, repo_name, number) ->
-          let repo = make_repo ~repo_url ~owner ~repo_name in
-          let send_slack_warning msg =
-            let channel_any = Slack_channel.to_any event.channel in
-            let warning_msg : Slack_t.post_message_req =
-              {
-                channel = channel_any;
-                thread_ts = Some thread_ts;
-                text = Some msg;
-                attachments = None;
-                blocks = None;
-                username = None;
-                unfurl_links = None;
-                unfurl_media = None;
-                reply_broadcast = false;
-              }
-            in
-            let%lwt _ = Slack_api.send_notification ~ctx ~msg:warning_msg in
-            Lwt.return_unit
+      match parse_pr_url pr_url with
+      | None ->
+        log#warn "failed to parse PR URL: %s" pr_url;
+        Lwt.return "error: failed to parse PR URL"
+      | Some (_repo_url, owner, repo_name, number) ->
+        let repo = make_repo ~repo_url ~owner ~repo_name in
+        let send_slack_warning msg =
+          let channel_any = Slack_channel.to_any event.channel in
+          let warning_msg : Slack_t.post_message_req =
+            {
+              channel = channel_any;
+              thread_ts = Some thread_ts;
+              text = Some msg;
+              attachments = None;
+              blocks = None;
+              username = None;
+              unfurl_links = None;
+              unfurl_media = None;
+              reply_broadcast = false;
+            }
+          in
+          let%lwt _ = Slack_api.send_notification ~ctx ~msg:warning_msg in
+          Lwt.return_unit
+        in
+        (* Validate PR exists *)
+        (match%lwt Github_api.get_pull_request ~ctx ~repo ~number with
+        | Error e ->
+          log#warn "failed to fetch PR #%d: %s" number e;
+          Lwt.return "error: failed to check PR status"
+        | Ok _pr ->
+          (* Ensure repo config is loaded *)
+          let%lwt cfg =
+            match Context.find_repo_config ctx repo_url with
+            | Some cfg -> Lwt.return_some cfg
+            | None ->
+              (try%lwt
+                 let%lwt _ok = fetch_config ~ctx ~repo in
+                 Lwt.return (Context.find_repo_config ctx repo_url)
+               with exn ->
+                 log#warn "failed to fetch config for %s: %s" repo_url (Printexc.to_string exn);
+                 Lwt.return_none)
in
-          (* Validate PR exists *)
-          (match%lwt Github_api.get_pull_request ~ctx ~repo ~number with
-          | Error e ->
-            log#warn "failed to fetch PR #%d: %s" number e;
-            Lwt.return "error: failed to check PR status"
-          | Ok _pr ->
-            (* Ensure repo config is loaded *)
-            let%lwt cfg =
-              match Context.find_repo_config ctx repo_url with
-              | Some cfg -> Lwt.return_some cfg
-              | None ->
-                (try%lwt
-                   let%lwt _ok = fetch_config ~ctx ~repo in
-                   Lwt.return (Context.find_repo_config ctx repo_url)
-                 with exn ->
-                   log#warn "failed to fetch config for %s: %s" repo_url (Printexc.to_string exn);
-                   Lwt.return_none)
+          (match cfg with
+          | None -> Lwt.return "error: failed to load repo config"
+          | Some _cfg ->
+            (* Resolve Slack user display name *)
+            let slack_id_str = Slack_user_id.project user in
+            let slack_display_name =
+              Option.default slack_id_str (Stringtbl.find_opt slack_id_to_display_name_tbl slack_id_str)
in
-            (match cfg with
-            | None -> Lwt.return "error: failed to load repo config"
-            | Some _cfg ->
-              (* Resolve Slack user display name *)
-              let slack_id_str = Slack_user_id.project user in
-              let slack_display_name =
-                Option.default slack_id_str (Stringtbl.find_opt slack_id_to_display_name_tbl slack_id_str)
-              in
-              (* Convert Slack mrkdwn to GitHub markdown *)
-              let resolve_user slack_id_str = Stringtbl.find_opt slack_id_to_display_name_tbl slack_id_str in
-              let decoded_text = Slack_to_github.decode_slack_entities text in
-              let quoted_text, body_text = Slack_to_github.extract_blockquote decoded_text in
-              let body_md = Slack_to_github.to_github_markdown ~resolve_user body_text in
-              let quoted_md = Slack_to_github.to_github_markdown ~resolve_user quoted_text in
-              let formatted_body = format_github_comment ~slack_display_name ~quoted_text:quoted_md body_md in
-              let messages = State.get_pr_messages ctx.state ~repo_url ~pr_url in
-              (match quoted_text with
-              | "" ->
-                (* No quote: post as top-level issue comment *)
+            (* Convert Slack mrkdwn to GitHub markdown *)
+            let resolve_user slack_id_str = Stringtbl.find_opt slack_id_to_display_name_tbl slack_id_str in
+            let decoded_text = Slack_to_github.decode_slack_entities text in
+            let quoted_text, body_text = Slack_to_github.extract_blockquote decoded_text in
+            let body_md = Slack_to_github.to_github_markdown ~resolve_user body_text in
+            let quoted_md = Slack_to_github.to_github_markdown ~resolve_user quoted_text in
+            let formatted_body = format_github_comment ~slack_display_name ~quoted_text:quoted_md body_md in
+            let messages = State.get_pr_messages ctx.state ~repo_url ~pr_url in
+            (match quoted_text with
+            | "" ->
+              (* No quote: post as top-level issue comment *)
+              (match%lwt Github_api.post_issue_comment ~ctx ~repo ~number ~body:formatted_body with
+              | Ok () -> Lwt.return "ok: posted issue comment"
+              | Error e ->
+                log#warn "failed to post issue comment: %s" e;
+                Lwt.return "error: failed to post issue comment")
+            | _ ->
+            (* Has quote: try to match against stored comments *)
+            match match_quote_to_comment ~messages quoted_text with
+            | [ matched ] ->
+              (match matched.comment_type with
+              | Review_comment ->
+                (match%lwt
+                   Github_api.reply_to_review_comment ~ctx ~repo ~number ~comment_id:matched.github_comment_id
+                     ~body:formatted_body
+                 with
+                | Ok () ->
+                  State.add_pr_message ctx.state ~repo_url ~pr_url
+                    {
+                      slack_ts = event.ts;
+                      github_comment_id = matched.github_comment_id;
+                      comment_type = Review_comment;
+                      body = body_text;
+                    };
+                  Lwt.return "ok: replied to review comment"
+                | Error e ->
+                  log#warn "failed to reply to review comment: %s" e;
+                  Lwt.return "error: failed to reply to review comment")
+              | Issue_comment ->
+                (* GitHub doesn't support replying to issue comments, post as new comment *)
(match%lwt Github_api.post_issue_comment ~ctx ~repo ~number ~body:formatted_body with
-                | Ok () -> Lwt.return "ok: posted issue comment"
+                | Ok () ->
+                  State.add_pr_message ctx.state ~repo_url ~pr_url
+                    {
+                      slack_ts = event.ts;
+                      github_comment_id = matched.github_comment_id;
+                      comment_type = Issue_comment;
+                      body = body_text;
+                    };
+                  Lwt.return "ok: posted issue comment in reply"
| Error e ->
log#warn "failed to post issue comment: %s" e;
-                  Lwt.return "error: failed to post issue comment")
-              | _ ->
-                (* Has quote: try to match against stored comments *)
-                (match match_quote_to_comment ~messages quoted_text with
-                | [ matched ] ->
-                  (match matched.comment_type with
-                  | Review_comment ->
-                    (match%lwt
-                       Github_api.reply_to_review_comment ~ctx ~repo ~number ~comment_id:matched.github_comment_id
-                         ~body:formatted_body
-                     with
-                    | Ok () ->
-                      State.add_pr_message ctx.state ~repo_url ~pr_url
-                        {
-                          slack_ts = event.ts;
-                          github_comment_id = matched.github_comment_id;
-                          comment_type = Review_comment;
-                          body = body_text;
-                        };
-                      Lwt.return "ok: replied to review comment"
-                    | Error e ->
-                      log#warn "failed to reply to review comment: %s" e;
-                      Lwt.return "error: failed to reply to review comment")
-                  | Issue_comment ->
-                    (* GitHub doesn't support replying to issue comments, post as new comment *)
-                    (match%lwt Github_api.post_issue_comment ~ctx ~repo ~number ~body:formatted_body with
-                    | Ok () ->
-                      State.add_pr_message ctx.state ~repo_url ~pr_url
-                        {
-                          slack_ts = event.ts;
-                          github_comment_id = matched.github_comment_id;
-                          comment_type = Issue_comment;
-                          body = body_text;
-                        };
-                      Lwt.return "ok: posted issue comment in reply"
-                    | Error e ->
-                      log#warn "failed to post issue comment: %s" e;
-                      Lwt.return "error: failed to post issue comment"))
-                | [] ->
-                  (* No match: post as issue comment with quote for context *)
-                  (match%lwt Github_api.post_issue_comment ~ctx ~repo ~number ~body:formatted_body with
-                  | Ok () -> Lwt.return "ok: posted issue comment (no quote match)"
-                  | Error e ->
-                    log#warn "failed to post issue comment: %s" e;
-                    Lwt.return "error: failed to post issue comment")
-                | _ ->
-                  (* Multiple matches: ambiguous *)
-                  let%lwt () =
-                    send_slack_warning
-                      (Printf.sprintf "Your quote matched multiple comments. You can add your comment directly: %s"
-                         pr_url)
-                  in
-                  Lwt.return "warning: ambiguous quote match"))))))
+                  Lwt.return "error: failed to post issue comment"))
+            | [] ->
+              (* No match: post as issue comment with quote for context *)
+              (match%lwt Github_api.post_issue_comment ~ctx ~repo ~number ~body:formatted_body with
+              | Ok () -> Lwt.return "ok: posted issue comment (no quote match)"
+              | Error e ->
+                log#warn "failed to post issue comment: %s" e;
+                Lwt.return "error: failed to post issue comment")
+            | _ ->
+              (* Multiple matches: ambiguous *)
+              let%lwt () =
+                send_slack_warning
+                  (Printf.sprintf "Your quote matched multiple comments. You can add your comment directly: %s" pr_url)
+              in
+              Lwt.return "warning: ambiguous quote match"))))


let process_slack_event (ctx : Context.t) headers body =
let secrets = Context.get_secrets_exn ctx in
dune build @fmt failed
"/usr/bin/env" "bash" "-c" "opam exec -- dune build @fmt --ignore-promoted-rules || (echo "dune build @fmt failed"; exit 2)" failed with exit status 2
2026-04-16 19:25.20: Job failed: Failed: Build failed