2026-04-25 18:31.05: 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 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 || git fetch origin master) && git reset -q --hard 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 && 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-25 18:31.05: Using cache hint "ahrefs/monorobot-ocaml/opam:debian-13-ocaml-4.08@sha256:3cbf0e68d3a718b80b9a2ab282b68bb62badcbc072fa014a5564516c5f70e74c-debian-13-4.08_opam-2.5-ocamlformat-9a427a5cdc1d87c781d8e9febed953e2fd5640c2" 2026-04-25 18:31.05: 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 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 || git fetch origin master) && git reset -q --hard 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 && 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-25 18:31.05: Waiting for resource in pool OCluster 2026-04-25 18:51.33: Waiting for worker… 2026-04-25 18:59.29: Got resource from pool OCluster Building on laodoke.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: Already exists 1a27fd2181de: Pulling fs layer c29591c91388: Pulling fs layer 356a64daa753: Pulling fs layer ac02f8f2062b: Pulling fs layer 1a27fd2181de: Waiting c29591c91388: Waiting 356a64daa753: Waiting aaa2f390e4c8: Pulling fs layer ac02f8f2062b: Waiting aaa2f390e4c8: Waiting b668811757f6: Pulling fs layer 32fd4e1a774f: Pulling fs layer b1d486fb8fdf: Pulling fs layer cf8a2024f299: Pulling fs layer 8c4a5dac67c3: Pulling fs layer b668811757f6: Waiting 32fd4e1a774f: Waiting b1d486fb8fdf: Waiting 6adfe24d7b40: Pulling fs layer 8c4a5dac67c3: Waiting dccd6e3c0589: Pulling fs layer 6adfe24d7b40: Waiting 2ee4bcb55cd4: Pulling fs layer dccd6e3c0589: Waiting 2dc3b58478d7: Pulling fs layer daf15e5c44c6: Pulling fs layer 2dc3b58478d7: Waiting 564e978a8088: Pulling fs layer daf15e5c44c6: Waiting 564e978a8088: Waiting 25a652a8c456: Pulling fs layer b3bfea7bff3f: Pulling fs layer a00f2937f570: Pulling fs layer 25a652a8c456: Waiting b3bfea7bff3f: Waiting 78bd1737ebff: Pulling fs layer 4f4fb700ef54: Pulling fs layer 4291a055edd7: Pulling fs layer 3c2b2836d59a: Pulling fs layer 4f4fb700ef54: Waiting 7b3e96544a52: Pulling fs layer 3c2b2836d59a: Waiting 4291a055edd7: Waiting c72503effb14: Pulling fs layer 7b3e96544a52: Waiting bca08df11a10: Pulling fs layer c72503effb14: Waiting 2ab0829b2daf: Pulling fs layer bca08df11a10: Waiting 1d246d4da211: Pulling fs layer a09cb4870027: Pulling fs layer 1d246d4da211: Waiting 2ab0829b2daf: Waiting 2ce07b4fe7c0: Pulling fs layer a09cb4870027: Waiting 6b96f28d505e: Pulling fs layer 2ce07b4fe7c0: Waiting b6ad36bba9bf: Pulling fs layer 6b96f28d505e: Waiting 56d62791a0f9: Pulling fs layer 4d60780055d1: Pulling fs layer 56d62791a0f9: Waiting 43f57a7c44cb: Pulling fs layer 4d60780055d1: Waiting f21057dc4e85: Pulling fs layer e0d2ca1300ed: Pulling fs layer 43f57a7c44cb: Waiting f21057dc4e85: Waiting 9da2f7598f8e: Pulling fs layer e0d2ca1300ed: Waiting cb569bb28593: Pulling fs layer b32c9abef0bb: Pulling fs layer dd7da3e42740: Pulling fs layer 06223904e4d3: Pulling fs layer cb569bb28593: Waiting 9da2f7598f8e: Waiting b32c9abef0bb: Waiting dd7da3e42740: Waiting b18a38618cf8: Pulling fs layer 06223904e4d3: Waiting b18a38618cf8: Waiting 1a27fd2181de: Download complete c29591c91388: Verifying Checksum c29591c91388: Download complete 1a27fd2181de: Pull complete c29591c91388: Pull complete 356a64daa753: Verifying Checksum 356a64daa753: Download complete ac02f8f2062b: Verifying Checksum ac02f8f2062b: Download complete 356a64daa753: Pull complete ac02f8f2062b: Pull complete aaa2f390e4c8: Verifying Checksum aaa2f390e4c8: Download complete b668811757f6: Verifying Checksum b668811757f6: Download complete 32fd4e1a774f: Verifying Checksum 32fd4e1a774f: Download complete 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: Verifying Checksum 2dc3b58478d7: Download complete daf15e5c44c6: Download complete 564e978a8088: Download complete 25a652a8c456: Verifying Checksum 25a652a8c456: Download complete b3bfea7bff3f: Verifying Checksum b3bfea7bff3f: Download complete a00f2937f570: Verifying Checksum a00f2937f570: Download complete 78bd1737ebff: Verifying Checksum 78bd1737ebff: Download complete 4f4fb700ef54: Download complete 4291a055edd7: Verifying Checksum 4291a055edd7: Download complete aaa2f390e4c8: Pull complete 3c2b2836d59a: Download complete b668811757f6: Pull complete 32fd4e1a774f: Pull complete 7b3e96544a52: Verifying Checksum 7b3e96544a52: Download complete b1d486fb8fdf: Pull complete c72503effb14: Verifying Checksum c72503effb14: Download complete cf8a2024f299: Pull complete bca08df11a10: Verifying Checksum bca08df11a10: Download complete 2ab0829b2daf: Verifying Checksum 2ab0829b2daf: Download complete 8c4a5dac67c3: Pull complete 6adfe24d7b40: Pull complete dccd6e3c0589: Pull complete 2ee4bcb55cd4: Pull complete 2dc3b58478d7: Pull complete daf15e5c44c6: Pull complete 564e978a8088: Pull complete 1d246d4da211: Download complete a09cb4870027: Download 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: Verifying Checksum 2ce07b4fe7c0: Download complete 2ce07b4fe7c0: Pull complete 6b96f28d505e: Verifying Checksum 6b96f28d505e: Download complete 6b96f28d505e: Pull complete b6ad36bba9bf: Verifying Checksum b6ad36bba9bf: Download complete b6ad36bba9bf: Pull complete 56d62791a0f9: Verifying Checksum 56d62791a0f9: Download complete 56d62791a0f9: Pull complete 43f57a7c44cb: Verifying Checksum 43f57a7c44cb: Download complete f21057dc4e85: Verifying Checksum f21057dc4e85: Download complete e0d2ca1300ed: Verifying Checksum e0d2ca1300ed: Download complete 9da2f7598f8e: Verifying Checksum 9da2f7598f8e: Download complete cb569bb28593: Download complete b32c9abef0bb: Download complete 4d60780055d1: Verifying Checksum 4d60780055d1: Download complete dd7da3e42740: Download complete b18a38618cf8: Download complete 06223904e4d3: Download 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-25 18:59.29 ---> 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 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 || git fetch origin master) && git reset -q --hard 9a427a5cdc1d87c781d8e9febed953e2fd5640c2 && git log --no-decorate -n1 --oneline && opam update -u")) From https://github.com/ocaml/opam-repository * branch master -> FETCH_HEAD 42844088d7..f83868273d master -> origin/master 9a427a5cdc Merge pull request #29742 from nmatschke/opam-publish-base.v0.14.4 <><> 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-25 18:59.29 ---> using "55207f8077d4ca8ea777cfe81c5bb0b4cfe243a94bc9f38e3494087a2b0a025d" 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-25 18:59.29 ---> using "205191a7b54ce074267c88bea92111408bb60945a5ee017f331e2ec05331c341" 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 either 1.0.0 [required by ocamlformat-lib] - install menhirLib 20260209 [required by ocamlformat-lib] - install ocaml-version 4.1.0 [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.4 [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.4] 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.1.0] 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 menhirCST.20260209 -> installed menhirGLR.20260209 -> installed menhirLib.20260209 -> installed cmdliner.1.3.0 -> installed menhirSdk.20260209 -> installed ocaml-version.4.1.0 -> 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.4 -> 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-25 19:01.36 ---> saved as "2193598054d501ebddba132689b15be9eebaef8eb5ad8f2c65461fda566fdd07" /src: (copy (src .) (dst /src/)) 2026-04-25 19:01.37 ---> saved as "9f78489df1eec4628a39c4b47b6b64a74fb30a14aba0ab003c7277b511bd5fe9" /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-25 19:01.39: Job failed: Failed: Build failed