Repository navigation
Unblock release verification build #16414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0d2b732
0d7239b
56837e5
ef7f5f8
6e9a537
2bac0fa
db6e391
337f0b5
8f5550c
8374a1b
0510b7c
806cb15
0beabb7
36651d9
f58f193
b4e2672
6459994
7428e6f
4beac05
097bf9a
6c131b2
c4bf7b1
fee0ee0
9737bc7
f439359
259d531
6910617
ef2c886
06b298a
a5ce445
fa9b5fd
278d636
58c1700
9de35d6
1b61c17
2dbbcd5
6f7170f
1bac05f
86a6aea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -365,10 +365,10 @@ jobs: | |
| chmod 600 "$production_env" | ||
|
|
||
| - name: Run Iroh gate | ||
| # Every non-soak phase is capped at 25 minutes. Stress soaks retain | ||
| # their one-hour workload window while the gate script bounds each | ||
| # setup, build, and launch phase separately. | ||
| timeout-minutes: ${{ inputs.soak_profile == 'stress' && 75 || 25 }} | ||
| # Relay-only stress is one hour of use plus the 30-minute credential | ||
| # rollover observation and bounded cleanup. The script's report | ||
| # timeout is 5,850 seconds, so this step must be longer than that. | ||
| timeout-minutes: ${{ inputs.soak_profile == 'stress' && 125 || 25 }} | ||
| env: | ||
| # The hosted runner does not have cmuxterm-hq's tagged-backend | ||
| # helper. The explicit Worker origin remains the app's API and v2 | ||
|
|
@@ -379,6 +379,9 @@ jobs: | |
| # has no cmux-tui artifact yet, use the newest older artifact with | ||
| # identical client inputs instead of failing before the app starts. | ||
| CMUX_TUI_CLIENT_MAX_FALLBACK: 5 | ||
| CMUX_CODEX_MODEL: gpt-5.3-codex-spark | ||
| CMUX_CODEX_DURATION_SECONDS: "3600" | ||
| CMUX_CODEX_STRICT_MODEL: "1" | ||
| run: | | ||
| set -euo pipefail | ||
| case "${{ matrix.mode }}" in | ||
|
|
@@ -408,11 +411,45 @@ jobs: | |
| if [[ "${{ matrix.mode }}" == automatic || "${{ matrix.mode }}" == relay-only ]]; then | ||
| GATE_ARGS+=(--soak-profile "${{ inputs.soak_profile }}") | ||
| fi | ||
| if [[ "${{ inputs.environment }}" == production && "${{ matrix.mode }}" == relay-only && "${{ inputs.soak_profile }}" == stress ]]; then | ||
| if [[ "${{ inputs.soak_profile }}" == stress && ( "${{ matrix.mode }}" == automatic || "${{ matrix.mode }}" == relay-only ) ]]; then | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The new run-script condition interpolates Prompt for AI agents |
||
| GATE_ARGS+=(--real-usage) | ||
| fi | ||
| ./scripts/run-iroh-release-gate.sh "${GATE_ARGS[@]}" | ||
|
|
||
| - name: Verify canonical v2 Worker identity | ||
| if: ${{ always() }} | ||
| env: | ||
| EXPECTED_ENVIRONMENT: ${{ inputs.environment }} | ||
| EXPECTED_V2_URL: ${{ inputs.environment == 'production' && 'https://cmux-v2.debussy.workers.dev' || inputs.v2_base_url }} | ||
| HEALTH_OUTPUT: ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-worker-health.json | ||
| run: | | ||
| set -euo pipefail | ||
| curl --fail --silent --show-error --retry 3 --retry-connrefused \ | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| --connect-timeout 10 --max-time 30 \ | ||
| "$EXPECTED_V2_URL/v2/health" > "$HEALTH_OUTPUT" | ||
| EXPECTED_ENVIRONMENT="$EXPECTED_ENVIRONMENT" EXPECTED_V2_URL="$EXPECTED_V2_URL" HEALTH_OUTPUT="$HEALTH_OUTPUT" \ | ||
| /usr/bin/python3 - <<'PY' | ||
| import json | ||
| import os | ||
| from urllib.parse import urlparse | ||
|
|
||
| with open(os.environ["HEALTH_OUTPUT"], encoding="utf-8") as handle: | ||
| health = json.load(handle) | ||
| expected_environment = os.environ["EXPECTED_ENVIRONMENT"] | ||
| expected_url = os.environ["EXPECTED_V2_URL"] | ||
| if health.get("schemaId") != "health.v1": | ||
| raise SystemExit("Worker health schema is not health.v1") | ||
| if health.get("environment") != expected_environment: | ||
| raise SystemExit("Worker health environment does not match the gate") | ||
| if not isinstance(health.get("sourceRevision"), str) or not health["sourceRevision"] or health["sourceRevision"] == "unknown": | ||
| raise SystemExit("Worker health did not identify its source revision") | ||
| if health.get("storage", {}).get("maxSchemaVersion", 0) < 7: | ||
| raise SystemExit("Worker storage schema is older than v2") | ||
| if urlparse(expected_url).hostname in {"cmux-iroh-v2.debussy.workers.dev", "cmux-iroh-v2-staging.debussy.workers.dev"}: | ||
| raise SystemExit("release gate used a compatibility alias instead of the canonical Worker") | ||
| print(json.dumps({"url": expected_url, "health": health}, sort_keys=True)) | ||
| PY | ||
|
|
||
| - name: Upload redacted verdict | ||
| if: ${{ always() }} | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
|
|
@@ -426,6 +463,9 @@ jobs: | |
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-ios-*.jsonl | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-ios-*.log | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-mac-failure.cmuxdiag | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-latency.state* | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-latency.json | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-worker-health.json | ||
| ${{ runner.temp }}/iroh-release-gate-${{ matrix.mode }}-real-usage/** | ||
| if-no-files-found: warn | ||
| retention-days: 7 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| enum CodexTranscriptFailureReadResult { | ||
| case unavailable | ||
| case pending | ||
| case aborted | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| case healthy(lastAssistantMessage: String?) | ||
| case failure(CodexHookFailureCandidate) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -293,7 +293,7 @@ struct RemoteRelaySlotTeardownTests { | |
|
|
||
| let succeeded = await coordinator.stopAndWait(cleanupScope: .persistentSlot) | ||
|
|
||
| let cleanupCommand = try #require(runner.requests.last?.arguments.last) | ||
| let cleanupCommand = try command(in: runner) { $0.contains("serve --persistent-stop --slot") } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Each predicate-based lookup is followed by a Prompt for AI agents |
||
| #expect(succeeded) | ||
| #expect(cleanupCommand.contains("serve --persistent-stop --slot")) | ||
| #expect(cleanupCommand.contains("64010.shell")) | ||
|
|
@@ -316,7 +316,7 @@ struct RemoteRelaySlotTeardownTests { | |
|
|
||
| let succeeded = await coordinator.stopAndWait(cleanupScope: .transport) | ||
|
|
||
| let cleanupCommand = try #require(runner.requests.last?.arguments.last) | ||
| let cleanupCommand = try command(in: runner) { $0.contains("64010.slot") } | ||
| #expect(succeeded) | ||
| #expect(!cleanupCommand.contains("serve --persistent-stop --slot")) | ||
| #expect(!cleanupCommand.contains("rm -rf")) | ||
|
|
@@ -335,7 +335,7 @@ struct RemoteRelaySlotTeardownTests { | |
|
|
||
| let succeeded = await coordinator.stopAndWait(cleanupScope: .persistentSlot) | ||
|
|
||
| let cleanupCommand = try #require(runner.requests.last?.arguments.last) | ||
| let cleanupCommand = try command(in: runner) { $0.contains("serve --persistent-stop --slot") } | ||
| #expect(succeeded) | ||
| #expect(cleanupCommand.contains("$HOME/.cmux/bin/cmuxd-remote")) | ||
| #expect(cleanupCommand.contains("serve --persistent-stop --slot")) | ||
|
|
@@ -355,9 +355,9 @@ struct RemoteRelaySlotTeardownTests { | |
|
|
||
| #expect(succeeded) | ||
| #expect(runner.requests.count == 2) | ||
| let metadataCleanup = try #require(runner.requests.first?.arguments.last) | ||
| let metadataCleanup = try command(in: runner) { $0.contains("64010.slot") } | ||
| #expect(metadataCleanup.contains("64010.slot")) | ||
| let directCleanup = try #require(runner.requests.last?.arguments.last) | ||
| let directCleanup = try command(in: runner) { $0.contains("$HOME/.cmux/bin/cmuxd-remote") } | ||
| #expect(directCleanup.contains("$HOME/.cmux/bin/cmuxd-remote")) | ||
| #expect(directCleanup.contains("serve --persistent-stop --slot")) | ||
| #expect(!directCleanup.contains("relay_socket=")) | ||
|
|
@@ -410,6 +410,19 @@ struct RemoteRelaySlotTeardownTests { | |
| ) | ||
| } | ||
|
|
||
| private func command( | ||
| in runner: SpyProcessRunner, | ||
| matching predicate: (String) -> Bool | ||
| ) throws -> String { | ||
| let commands = runner.requests.compactMap(\.arguments.last) | ||
| guard let command = commands.first(where: { predicate($0) }) else { | ||
| throw MissingCleanupCommand() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Prompt for AI agents |
||
| } | ||
| return command | ||
| } | ||
|
|
||
| private struct MissingCleanupCommand: Error {} | ||
|
|
||
| enum MalformedSlotCleanupScope: String, CaseIterable, Sendable { | ||
| case persistentSlot | ||
| case transport | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The 125-minute step limit can cancel relay-only stress while the gate is still within its own phase and report deadlines: both builds and prewarm run before the 5,850-second report wait. Include that setup/build time in the step and job timeout, or lower the phase limits so the full sequence fits.
Prompt for AI agents