3535 description : Optional existing Crabbox desktop lease id or slug to reuse
3636 required : false
3737 type : string
38+ allow_fork_candidate :
39+ description : Allow a fork PR head candidate when pr_number points at that PR
40+ required : false
41+ default : false
42+ type : boolean
3843
3944permissions :
4045 contents : write
95100 needs : authorize_actor
96101 runs-on : ubuntu-24.04
97102 outputs :
103+ allow_fork_candidate : ${{ steps.resolve.outputs.allow_fork_candidate }}
98104 baseline_ref : ${{ steps.resolve.outputs.baseline_ref }}
99105 candidate_ref : ${{ steps.resolve.outputs.candidate_ref }}
100106 crabbox_provider : ${{ steps.resolve.outputs.crabbox_provider }}
@@ -119,6 +125,10 @@ jobs:
119125 if (eventName === "workflow_dispatch") {
120126 const inputs = context.payload.inputs ?? {};
121127 setOutput("should_run", "true");
128+ setOutput(
129+ "allow_fork_candidate",
130+ String(inputs.allow_fork_candidate) === "true" ? "true" : "false",
131+ );
122132 setOutput("baseline_ref", inputs.baseline_ref || "main");
123133 setOutput("candidate_ref", inputs.candidate_ref || "main");
124134 setOutput("pr_number", inputs.pr_number || "");
@@ -150,6 +160,7 @@ jobs:
150160 if (!requested) {
151161 core.notice("Comment mentioned Mantis but did not request Telegram desktop proof.");
152162 setOutput("should_run", "false");
163+ setOutput("allow_fork_candidate", "false");
153164 setOutput("baseline_ref", "");
154165 setOutput("candidate_ref", "");
155166 setOutput("pr_number", "");
@@ -192,8 +203,10 @@ jobs:
192203 rawCandidate && !["head", "pr", "pr-head"].includes(rawCandidate.toLowerCase())
193204 ? rawCandidate
194205 : mergedCandidate || pr.head.sha;
206+ const allowForkCandidate = /\bfork[-_]ok\b/i.test(body);
195207
196208 setOutput("should_run", "true");
209+ setOutput("allow_fork_candidate", allowForkCandidate ? "true" : "false");
197210 setOutput("baseline_ref", baselineMatch?.[1] || mergedBaseline || "main");
198211 setOutput("candidate_ref", candidate);
199212 setOutput("pr_number", String(issue.number));
@@ -217,6 +230,7 @@ jobs:
217230 outputs :
218231 baseline_revision : ${{ steps.validate.outputs.baseline_revision }}
219232 candidate_revision : ${{ steps.validate.outputs.candidate_revision }}
233+ candidate_trust : ${{ steps.validate.outputs.candidate_trust }}
220234 steps :
221235 - name : Checkout harness ref
222236 uses : actions/checkout@v6
@@ -227,6 +241,7 @@ jobs:
227241 - name : Validate refs are trusted
228242 id : validate
229243 env :
244+ ALLOW_FORK_CANDIDATE : ${{ needs.resolve_request.outputs.allow_fork_candidate }}
230245 BASELINE_REF : ${{ needs.resolve_request.outputs.baseline_ref }}
231246 CANDIDATE_REF : ${{ needs.resolve_request.outputs.candidate_ref }}
232247 GH_TOKEN : ${{ github.token }}
@@ -264,25 +279,43 @@ jobs:
264279 )"
265280 if [[ "$pr_head_count" != "0" ]]; then
266281 reason="open-pr-head"
282+ elif [[ "$label" == "candidate" && "${ALLOW_FORK_CANDIDATE:-false}" == "true" && -n "${PR_NUMBER:-}" ]]; then
283+ local fork_pr_head_count
284+ fork_pr_head_count="$(
285+ gh api \
286+ -H "Accept: application/vnd.github+json" \
287+ "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
288+ --jq 'if .state == "open" and .head.repo.full_name != "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${revision}"'" then 1 else 0 end'
289+ )"
290+ if [[ "$fork_pr_head_count" == "1" ]]; then
291+ reason="maintainer-approved-fork-pr-head"
292+ fi
267293 fi
268294 fi
269295
270296 if [[ -z "$reason" ]]; then
271- echo "${label} ref '${input_ref}' resolved to ${revision}, which is not trusted for this secret-bearing Mantis run." >&2
297+ echo "${label} ref '${input_ref}' resolved to ${revision}, which is not trusted for this secret-bearing Mantis run. Add fork-ok only for a maintainer-approved fork PR head. " >&2
272298 exit 1
273299 fi
274- printf '%s\n' "$revision"
300+ printf '%s\t%s\ n' "$revision" "$reason "
275301 }
276302
277303 baseline_revision="$(validate_ref baseline "$BASELINE_REF")"
304+ baseline_trust="${baseline_revision#*$'\t'}"
305+ baseline_revision="${baseline_revision%%$'\t'*}"
278306 candidate_revision="$(validate_ref candidate "$CANDIDATE_REF")"
307+ candidate_trust="${candidate_revision#*$'\t'}"
308+ candidate_revision="${candidate_revision%%$'\t'*}"
279309 echo "baseline_revision=${baseline_revision}" >> "$GITHUB_OUTPUT"
280310 echo "candidate_revision=${candidate_revision}" >> "$GITHUB_OUTPUT"
311+ echo "candidate_trust=${candidate_trust}" >> "$GITHUB_OUTPUT"
281312 {
282313 echo "baseline: \`${BASELINE_REF}\`"
283314 echo "baseline SHA: \`${baseline_revision}\`"
315+ echo "baseline trust: \`${baseline_trust}\`"
284316 echo "candidate: \`${CANDIDATE_REF}\`"
285317 echo "candidate SHA: \`${candidate_revision}\`"
318+ echo "candidate trust: \`${candidate_trust}\`"
286319 } >> "$GITHUB_STEP_SUMMARY"
287320
288321 run_telegram_desktop_proof :
@@ -375,7 +408,7 @@ jobs:
375408 printf '%s\n' 'Defaults env_keep += "CODEX_HOME CODEX_INTERNAL_ORIGINATOR_OVERRIDE"'
376409 printf '%s\n' 'Defaults env_keep += "BASELINE_REF BASELINE_SHA CANDIDATE_REF CANDIDATE_SHA"'
377410 printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER"'
378- printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
411+ printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_CANDIDATE_TRUST MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
379412 printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
380413 printf '%s\n' 'Defaults env_keep += "OPENCLAW_TELEGRAM_USER_CRABBOX_BIN OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT OPENCLAW_TELEGRAM_USER_PROOF_CMD"'
381414 } | sudo tee /etc/sudoers.d/mantis-codex-env >/dev/null
@@ -406,6 +439,7 @@ jobs:
406439 CRABBOX_LEASE_ID : ${{ needs.resolve_request.outputs.lease_id }}
407440 CRABBOX_PROVIDER : ${{ needs.resolve_request.outputs.crabbox_provider }}
408441 GH_TOKEN : ${{ github.token }}
442+ MANTIS_CANDIDATE_TRUST : ${{ needs.validate_refs.outputs.candidate_trust }}
409443 MANTIS_INSTRUCTIONS : ${{ needs.resolve_request.outputs.instructions }}
410444 MANTIS_OUTPUT_DIR : ${{ env.MANTIS_OUTPUT_DIR }}
411445 MANTIS_PR_NUMBER : ${{ needs.resolve_request.outputs.pr_number }}
0 commit comments