Files
openclaw/.github/workflows/mantis-telegram-desktop-proof.yml
Ayaan Zaidi 614e084cf8 improve: serialize Telegram Mantis proofs (#128811)
Queue Telegram-visible Mantis proofs at workflow level so only one shared-user run allocates a runner at a time.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-24 23:10:45 +05:30

420 lines
20 KiB
YAML

name: Mantis Telegram Desktop Proof
on:
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to prove
required: true
type: string
instructions:
description: Optional maintainer guidance for the open-ended investigation
required: false
type: string
allow_fork_candidate:
description: Allow this secret-bearing run for the selected fork PR head
required: false
default: false
type: boolean
approved_head_sha:
description: Exact fork PR head SHA approved for this secret-bearing run
required: false
type: string
request_source:
description: Dispatcher request source; ignored for manual runs
required: false
default: workflow_dispatch
type: string
concurrency:
group: mantis-telegram-visible-proof
cancel-in-progress: false
queue: max
permissions:
actions: read
contents: read
issues: write
pull-requests: write
env:
CRABBOX_LINUX_AMD64_SHA256: c9d38e67af31e5383ab4117bae9b88a71a04c80da5d923f851fbd731ece3a3a4
CRABBOX_VERSION: 0.45.0
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
MANTIS_OUTPUT_DIR: .artifacts/qa-e2e/mantis/telegram-visible-proof
NODE_VERSION: "24.x"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
jobs:
resolve_request:
name: Resolve Mantis request
runs-on: ubuntu-24.04
environment: qa-live-shared
outputs:
baseline_revision: ${{ steps.resolve.outputs.baseline_revision }}
head_revision: ${{ steps.resolve.outputs.head_revision }}
instructions: ${{ steps.resolve.outputs.instructions }}
merge_base_revision: ${{ steps.resolve.outputs.merge_base_revision }}
pr_context: ${{ steps.resolve.outputs.pr_context }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
request_source: ${{ steps.resolve.outputs.request_source }}
should_run: ${{ steps.resolve.outputs.should_run }}
steps:
- name: Resolve refs and target PR
id: resolve
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const inputs = context.payload.inputs ?? {};
const prNumber = Number(inputs.pr_number);
if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
core.setFailed("Mantis requires a valid pull request number.");
return;
}
const { owner, repo } = context.repo;
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
if (pr.state !== "open" || !pr.head.repo) {
core.setFailed("Mantis requires an open pull request with an available head repository.");
return;
}
if (pr.base.ref !== "main") {
core.setFailed(`Mantis proves landing on main; this PR targets '${pr.base.ref}'.`);
return;
}
const immutableSha = /^[0-9a-f]{40}$/u;
const headRevision = pr.head.sha;
if (!immutableSha.test(headRevision)) {
core.setFailed(`PR head '${headRevision}' is not an immutable commit SHA.`);
return;
}
const { data: mainRef } = await github.rest.git.getRef({ owner, repo, ref: "heads/main" });
const baselineRevision = mainRef.object.sha;
const comparison = await github.request("GET /repos/{owner}/{repo}/compare/{basehead}", {
owner,
repo,
basehead: `${baselineRevision}...${headRevision}`,
});
const mergeBaseRevision = comparison.data.merge_base_commit.sha;
if (!immutableSha.test(baselineRevision) || !immutableSha.test(mergeBaseRevision)) {
core.setFailed("Mantis could not resolve immutable main and merge-base revisions.");
return;
}
if (pr.head.repo.full_name !== `${owner}/${repo}`) {
const allowFork = inputs.allow_fork_candidate === true || inputs.allow_fork_candidate === "true";
if (!allowFork || inputs.approved_head_sha !== headRevision) {
core.setFailed(`Fork approval must name the exact current PR head SHA (${headRevision}).`);
return;
}
}
const dispatcherSources = new Set(["clawsweeper_label", "issue_comment"]);
const requestSource =
context.actor === "github-actions[bot]" && dispatcherSources.has(inputs.request_source)
? inputs.request_source
: "workflow_dispatch";
core.setOutput("baseline_revision", baselineRevision);
core.setOutput("head_revision", headRevision);
core.setOutput("merge_base_revision", mergeBaseRevision);
core.setOutput("pr_number", String(pr.number));
core.setOutput("instructions", inputs.instructions || "");
core.setOutput(
"pr_context",
JSON.stringify({ title: pr.title.slice(0, 500), body: (pr.body ?? "").slice(0, 12000) }),
);
core.setOutput("request_source", requestSource);
core.setOutput("should_run", "true");
- name: Create Mantis status token
id: status_token
if: ${{ steps.resolve.outputs.pr_number != '' }}
continue-on-error: true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-issues: write
permission-pull-requests: write
- name: Report Mantis run started
if: ${{ steps.status_token.outcome == 'success' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
TARGET_PR: ${{ steps.resolve.outputs.pr_number }}
with:
github-token: ${{ steps.status_token.outputs.token }}
script: |
const marker = `<!-- mantis-telegram-visible-proof:${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT} -->`;
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const { owner, repo } = context.repo;
const issue_number = Number(process.env.TARGET_PR);
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const previous = comments.filter(
(comment) =>
comment.user?.login === "openclaw-mantis[bot]" &&
(comment.body?.includes("<!-- mantis-telegram-visible-proof:") ||
comment.body?.includes("<!-- mantis-telegram-desktop-proof:")),
);
for (const stale of previous) {
await github.rest.issues.deleteComment({ owner, repo, comment_id: stale.id });
}
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: `${marker}\n👀 Mantis is running an open-ended real-user Telegram investigation. [Follow the run](${runUrl}).`,
});
run_telegram_visible_proof:
name: Run agentic native Telegram proof
needs: resolve_request
if: needs.resolve_request.outputs.should_run == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 100
environment: qa-live-shared
outputs:
comparison_status: ${{ steps.collect.outputs.comparison_status }}
output_dir: ${{ steps.collect.outputs.output_dir }}
steps:
- name: Checkout harness ref
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
fetch-depth: 1
- name: Setup Node environment
id: setup-node-env
uses: ./.github/actions/setup-node-env
with:
cache-mode: read-write
node-version: ${{ env.NODE_VERSION }}
install-bun: "true"
- name: Setup uv for Telegram user driver
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- name: Install trusted Telegram proof tools
shell: bash
run: bash scripts/mantis/telegram-visible-install-tools.sh
- name: Install Crabbox CLI
shell: bash
run: bash scripts/mantis/telegram-visible-install-crabbox.sh
- name: Build local Telegram Desktop image
run: bash scripts/mantis/build-telegram-desktop-image.sh
- name: Create exact proof worktrees
id: worktrees
env:
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
HEAD_SHA: ${{ needs.resolve_request.outputs.head_revision }}
MERGE_BASE_SHA: ${{ needs.resolve_request.outputs.merge_base_revision }}
PR_NUMBER: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: bash scripts/mantis/telegram-visible-create-worktrees.sh
- name: Restore exact baseline build
id: baseline_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .artifacts/mantis-runtime-build.tar
key: ${{ runner.os }}-${{ runner.arch }}-mantis-runtime-v2-${{ steps.worktrees.outputs.lockfile_sha256 }}-${{ steps.worktrees.outputs.node_version }}-${{ steps.worktrees.outputs.pnpm_version }}-${{ needs.resolve_request.outputs.baseline_revision }}
- name: Build baseline and candidate
env:
BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
BASELINE_ARCHIVE: ${{ github.workspace }}/.artifacts/mantis-runtime-build.tar
BASELINE_CACHE_HIT: ${{ steps.baseline_cache.outputs.cache-hit }}
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
HOST_PNPM_STORE: ${{ steps.setup-node-env.outputs.pnpm-store-cache-path }}
shell: bash
run: bash scripts/mantis/telegram-visible-build-worktrees.sh
- name: Save exact baseline build
if: steps.setup-node-env.outputs.cache-mode == 'read-write' && steps.baseline_cache.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .artifacts/mantis-runtime-build.tar
key: ${{ steps.baseline_cache.outputs.cache-primary-key }}
- name: Install TDLib and restore Telegram QA user
id: telegram_credential
env:
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CREDENTIAL_OWNER_ID: mantis-telegram-visible-${{ github.run_id }}-${{ github.run_attempt }}
shell: bash
run: bash scripts/mantis/telegram-visible-lease-user.sh
- name: Prepare Codex user and bound Telegram bridges
env:
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
shell: bash
run: bash scripts/mantis/telegram-visible-prepare-codex.sh
- name: Prepare Codex action runtime
uses: openai/codex-action@52fe01ec70a42f454c9d2ebd47598f9fd6893d56
with:
openai-api-key: ${{ secrets.OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
codex-home: /tmp/mantis-codex-home-${{ github.run_id }}
safety-strategy: unprivileged-user
codex-user: codex
allow-bot-users: github-actions[bot]
- name: Run open-ended Telegram investigation with GPT-5.6
env:
LEASE_LOST_MARKER: ${{ steps.telegram_credential.outputs.lease_lost_marker }}
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
CODEX_HOME: /tmp/mantis-codex-home-${{ github.run_id }}
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: codex_github_action
GITHUB_WORKSPACE: ${{ github.workspace }}
MANTIS_BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
MANTIS_CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
MANTIS_BASELINE: /usr/local/bin/mantis-telegram-baseline
MANTIS_CANDIDATE: /usr/local/bin/mantis-telegram-candidate
MANTIS_FIXTURE_BASELINE: ${{ steps.telegram_credential.outputs.session_root }}/fixture-plugins/baseline
MANTIS_FIXTURE_CANDIDATE: ${{ steps.telegram_credential.outputs.session_root }}/fixture-plugins/candidate
MANTIS_INSTRUCTIONS: ${{ needs.resolve_request.outputs.instructions }}
MANTIS_PR_CONTEXT: ${{ needs.resolve_request.outputs.pr_context }}
shell: bash
run: bash scripts/mantis/telegram-visible-run-agent.sh
- name: Clean up Mantis sessions
id: cleanup
if: ${{ always() }}
env:
LEASE_KEEPALIVE_PID_FILE: ${{ steps.telegram_credential.outputs.lease_keepalive_pid_file }}
LEASE_FILE: ${{ steps.telegram_credential.outputs.lease_file }}
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
shell: bash
run: bash scripts/mantis/telegram-visible-cleanup-proof.sh
- name: Collect trusted Telegram evidence
id: collect
if: ${{ success() && steps.cleanup.outputs.safe_to_release == 'true' }}
env:
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
shell: bash
run: bash scripts/mantis/telegram-visible-collect-proof.sh
- name: Release Telegram QA user lease
id: release
if: ${{ always() && steps.cleanup.outputs.safe_to_release == 'true' }}
env:
LEASE_FILE: ${{ steps.telegram_credential.outputs.lease_file }}
LEASE_LOST_MARKER: ${{ steps.telegram_credential.outputs.lease_lost_marker }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
shell: bash
run: bash scripts/mantis/telegram-visible-release-user.sh
- name: Remove private Mantis runtime state
if: ${{ always() && steps.cleanup.outputs.safe_to_release == 'true' }}
env:
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
SUT_CREDENTIAL_DIR: ${{ steps.telegram_credential.outputs.sut_credential_dir }}
CREDENTIAL_DIR: ${{ steps.telegram_credential.outputs.credential_dir }}
shell: bash
run: bash scripts/mantis/telegram-visible-remove-private.sh
- name: Upload Mantis Telegram artifacts
id: upload
if: ${{ always() && steps.collect.outputs.output_dir != '' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: mantis-telegram-visible-proof-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.collect.outputs.output_dir }}
retention-days: 14
if-no-files-found: error
- name: Create Mantis GitHub App token
id: app_token
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-issues: write
permission-pull-requests: read
- name: Publish Telegram proof
id: publish
if: ${{ always() && steps.collect.outputs.output_dir != '' && steps.app_token.outcome == 'success' }}
env:
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
GH_TOKEN: ${{ steps.app_token.outputs.token }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: |
set -euo pipefail
marker="<!-- mantis-telegram-visible-proof:${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT} -->"
node scripts/mantis/publish-pr-evidence.mjs \
--manifest "${{ steps.collect.outputs.output_dir }}/mantis-evidence.json" \
--target-pr "$TARGET_PR" \
--artifact-root "mantis/telegram-visible/pr-${TARGET_PR}/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
--marker "$marker" \
--create-missing false \
--artifact-url "$ARTIFACT_URL" \
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--request-source "$REQUEST_SOURCE"
- name: Report incomplete Mantis proof
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.publish.outcome != 'success' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
with:
github-token: ${{ steps.app_token.outputs.token || github.token }}
script: |
const marker = `<!-- mantis-telegram-visible-proof:${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT} -->`;
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const { owner, repo } = context.repo;
const issue_number = Number(process.env.TARGET_PR);
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 });
const existing = comments.find((comment) => comment.body?.includes(marker));
const body = `${marker}\nMantis could not complete this proof. [Open the failed run](${runUrl}).`;
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}
- name: Fail on proof or cleanup failure
if: >-
${{
always() &&
(
steps.collect.outputs.comparison_status != 'pass' ||
steps.cleanup.outcome == 'failure' ||
steps.release.outcome == 'failure' ||
steps.publish.outcome == 'failure'
)
}}
env:
COMPARISON_STATUS: ${{ steps.collect.outputs.comparison_status }}
run: |
echo "Mantis Telegram visible proof failed: ${COMPARISON_STATUS:-no trusted verdict}." >&2
exit 1