Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d6e7bf8
πŸ€– Add update notification UI with electron-updater
ammar-agent Oct 19, 2025
d5891f2
πŸ€– Disable update checks when telemetry is off
ammar-agent Oct 19, 2025
3c6619d
πŸ€– Simplify update IPC and add DEBUG_UPDATER support
ammar-agent Oct 19, 2025
9c758c8
πŸ€– Move update checks to frontend to respect telemetry
ammar-agent Oct 19, 2025
99bf951
πŸ€– Always show update indicator for clarity
ammar-agent Oct 19, 2025
285e3ed
πŸ€– Improve title bar UX: ellipsis and version in tooltips
ammar-agent Oct 19, 2025
2137d01
πŸ€– Improve title bar spacing and tooltip readability
ammar-agent Oct 19, 2025
cb61734
πŸ€– Add on-hover update check with live feedback and DRY tooltips
ammar-agent Oct 19, 2025
a154810
πŸ€– Fix update check hanging on tooltip hover
ammar-agent Oct 19, 2025
ee638f4
πŸ€– Add 30-second timeout to update checks
ammar-agent Oct 19, 2025
4fe2971
πŸ€– Add integration test for GitHub releases API
ammar-agent Oct 19, 2025
787150a
πŸ€– Fix UI stuck in 'Checking for updates'
ammar-agent Oct 20, 2025
83f90cd
πŸ€– Remove 'checks every 4 hours' from tooltip
ammar-agent Oct 20, 2025
cd43b8e
πŸ€– Add comprehensive logging to updater backend
ammar-agent Oct 20, 2025
f0faa81
πŸ€– Use standard log interface with timestamps
ammar-agent Oct 20, 2025
a216830
πŸ€– Switch to kitchen time format for logs
ammar-agent Oct 20, 2025
26224f1
πŸ€– Add terminal colors and fix log timestamp precision
ammar-agent Oct 20, 2025
01c9e6c
πŸ€– Fix inconsistent boolean env var parsing
ammar-agent Oct 20, 2025
80679fb
πŸ€– Add dev-app-update.yml for DEBUG_UPDATER mode
ammar-agent Oct 20, 2025
39fdf09
πŸ€– Distinguish update states: idle vs up-to-date
ammar-agent Oct 20, 2025
2866966
πŸ€– Refactor: Move parseBoolEnv to utils and remove pointless test
ammar-agent Oct 20, 2025
4470f57
πŸ€– Add DEBUG_UPDATER fake version injection for testing
ammar-agent Oct 20, 2025
7f979e6
πŸ€– Fix fake version download/install flows
ammar-agent Oct 20, 2025
0d18908
πŸ€– Cleanup: reduce logging verbosity and extract magic constants
ammar-agent Oct 20, 2025
69ee427
πŸ€– Fix lint errors and chalk ESM import issues
ammar-agent Oct 20, 2025
9b733a6
πŸ€– fix: merge duplicate moduleNameMapper in jest.config
ammar-agent Oct 20, 2025
c7b85f5
πŸ€– improve extract_pr_logs.sh with local reproduction hints
ammar-agent Oct 20, 2025
30c726c
πŸ€– fix: use gh api for log fetching + correct shfmt formatting
ammar-agent Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 278 additions & 6 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dev-app-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider: github
owner: coder
repo: cmux
releaseType: release
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
"^chalk$": "<rootDir>/tests/__mocks__/chalk.js",
},
transform: {
"^.+\\.tsx?$": [
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@
"@types/react-syntax-highlighter": "^15.5.13",
"ai": "^5.0.72",
"ai-tokenizer": "^1.0.3",
"chalk": "^5.6.2",
"cmdk": "^1.0.0",
"crc-32": "^1.2.2",
"diff": "^8.0.2",
"disposablestack": "^1.1.7",
"electron-updater": "^6.6.2",
"jsonc-parser": "^3.3.1",
"lru-cache": "^11.2.2",
"markdown-it": "^14.1.0",
Expand Down
153 changes: 153 additions & 0 deletions scripts/extract_pr_logs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
# Extract logs from failed GitHub Actions runs for a PR
# Usage: ./scripts/extract_pr_logs.sh <pr_number_or_run_id> [job_name_pattern] [--wait]
#
# Examples:
# ./scripts/extract_pr_logs.sh 329 # Latest failed run for PR #329
# ./scripts/extract_pr_logs.sh 329 Integration # Only Integration Test jobs
# ./scripts/extract_pr_logs.sh 329 --wait # Wait for logs to be available
# ./scripts/extract_pr_logs.sh 18640062283 # Specific run ID

set -euo pipefail

INPUT="${1:-}"
JOB_PATTERN="${2:-}"
WAIT_FOR_LOGS=false

# Parse flags
if [[ "$JOB_PATTERN" == "--wait" ]]; then
WAIT_FOR_LOGS=true
JOB_PATTERN=""
elif [[ "${3:-}" == "--wait" ]]; then
WAIT_FOR_LOGS=true
fi

if [[ -z "$INPUT" ]]; then
echo "❌ Usage: $0 <pr_number_or_run_id> [job_name_pattern]" >&2
echo "" >&2
echo "Examples:" >&2
echo " $0 329 # Latest failed run for PR #329 (RECOMMENDED)" >&2
echo " $0 329 Integration # Only Integration Test jobs from PR #329" >&2
echo " $0 18640062283 # Specific run ID" >&2
exit 1
fi

# Detect if input is PR number or run ID (run IDs are much longer)
if [[ "$INPUT" =~ ^[0-9]{1,5}$ ]]; then
PR_NUMBER="$INPUT"
echo "πŸ” Finding latest failed run for PR #$PR_NUMBER..." >&2

# Get the latest failed run for this PR
RUN_ID=$(gh pr checks "$PR_NUMBER" --json name,link,state --jq '.[] | select(.state == "FAILURE") | .link' | head -1 | sed -E 's|.*/runs/([0-9]+).*|\1|' || echo "")

if [[ -z "$RUN_ID" ]]; then
echo "❌ No failed runs found for PR #$PR_NUMBER" >&2
echo "" >&2
echo "Current check status:" >&2
gh pr checks "$PR_NUMBER" 2>&1 || true
exit 1
fi

echo "πŸ“‹ Found failed run: $RUN_ID" >&2
else
RUN_ID="$INPUT"
echo "πŸ“‹ Fetching logs for run $RUN_ID..." >&2
fi

# Get all jobs for this run
JOBS=$(gh run view "$RUN_ID" --json jobs -q '.jobs[]' 2>/dev/null)

if [[ -z "$JOBS" ]]; then
echo "❌ No jobs found for run $RUN_ID" >&2
echo "" >&2
echo "Check if run ID is correct:" >&2
echo " gh run list --limit 10" >&2
exit 1
fi

# Filter to failed jobs only (unless specific pattern requested)
if [[ -z "$JOB_PATTERN" ]]; then
FAILED_JOBS=$(echo "$JOBS" | jq -r 'select(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "CANCELLED")')
if [[ -n "$FAILED_JOBS" ]]; then
echo "🎯 Showing only failed jobs (use job_pattern to see others)" >&2
JOBS="$FAILED_JOBS"
fi
fi

# Parse jobs and filter by pattern if provided
if [[ -n "$JOB_PATTERN" ]]; then
MATCHING_JOBS=$(echo "$JOBS" | jq -r "select(.name | test(\"$JOB_PATTERN\"; \"i\")) | .databaseId")
if [[ -z "$MATCHING_JOBS" ]]; then
echo "❌ No jobs matching pattern '$JOB_PATTERN'" >&2
echo "" >&2
echo "Available jobs:" >&2
echo "$JOBS" | jq -r '.name' >&2
exit 1
fi
JOB_IDS="$MATCHING_JOBS"
else
JOB_IDS=$(echo "$JOBS" | jq -r '.databaseId')
fi

# Map job names to local commands for reproduction
suggest_local_command() {
local job_name="$1"
case "$job_name" in
*"Static Checks"* | *"lint"* | *"typecheck"* | *"fmt"*)
echo "πŸ’‘ Reproduce locally: make static-check"
;;
*"Integration Tests"*)
echo "πŸ’‘ Reproduce locally: make test-integration"
;;
*"Test"*)
echo "πŸ’‘ Reproduce locally: make test"
;;
*"Build"*)
echo "πŸ’‘ Reproduce locally: make build"
;;
*"End-to-End"*)
echo "πŸ’‘ Reproduce locally: make test-e2e"
;;
esac
}

# Extract and display logs for each job
for JOB_ID in $JOB_IDS; do
JOB_INFO=$(echo "$JOBS" | jq -r "select(.databaseId == $JOB_ID)")
JOB_NAME=$(echo "$JOB_INFO" | jq -r '.name')
JOB_STATUS=$(echo "$JOB_INFO" | jq -r '.conclusion // .status')

echo "" >&2
echo "════════════════════════════════════════════════════════════" >&2
echo "Job: $JOB_NAME (ID: $JOB_ID) - $JOB_STATUS" >&2
echo "════════════════════════════════════════════════════════════" >&2

# Suggest local reproduction command
suggest_local_command "$JOB_NAME" >&2
echo "" >&2

# Fetch logs with retry logic if --wait flag is set
MAX_RETRIES=3
RETRY_COUNT=0

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
# Use gh api to fetch logs (works for individual completed jobs even if run is in progress)
if gh api "/repos/coder/cmux/actions/jobs/$JOB_ID/logs" 2>/dev/null; then
break
else
RETRY_COUNT=$((RETRY_COUNT + 1))
if [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$WAIT_FOR_LOGS" = true ]; then
echo "⏳ Logs not ready yet, waiting 5 seconds... (attempt $RETRY_COUNT/$MAX_RETRIES)" >&2
sleep 5
else
echo "⚠️ Could not fetch logs for job $JOB_ID" >&2
if [ "$WAIT_FOR_LOGS" = false ]; then
echo " Tip: Use --wait flag to retry if logs are still processing" >&2
else
echo " (logs may have expired or are still processing)" >&2
fi
break
fi
fi
done
done
10 changes: 7 additions & 3 deletions scripts/wait_pr_checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fi
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Get remote tracking branch
REMOTE_BRANCH=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "")
REMOTE_BRANCH=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "")

if [[ -z "$REMOTE_BRANCH" ]]; then
echo "❌ Error: Current branch '$CURRENT_BRANCH' has no upstream branch." >&2
Expand Down Expand Up @@ -119,14 +119,18 @@ while true; do
echo "❌ Some checks failed:"
echo ""
gh pr checks "$PR_NUMBER"
echo ""
echo "πŸ’‘ To extract detailed logs from the failed run:"
echo " ./scripts/extract_pr_logs.sh $PR_NUMBER"
echo " ./scripts/extract_pr_logs.sh $PR_NUMBER <job_pattern> # e.g., Integration"
exit 1
fi

# Check for unresolved review comments in the hot loop
if ! ./scripts/check_pr_reviews.sh "$PR_NUMBER" >/dev/null 2>&1; then
echo ""
echo "❌ Unresolved review comments found!"
echo " πŸ‘‰ Tip: run ./scripts/check_pr_reviews.sh "$PR_NUMBER" to list them."
echo " πŸ‘‰ Tip: run ./scripts/check_pr_reviews.sh $PR_NUMBER to list them."
./scripts/check_pr_reviews.sh "$PR_NUMBER"
exit 1
fi
Expand All @@ -147,7 +151,7 @@ while true; do
else
echo ""
echo "❌ Please resolve Codex comments before merging."
echo " πŸ‘‰ Tip: use ./scripts/check_pr_reviews.sh "$PR_NUMBER" to list unresolved comments."
echo " πŸ‘‰ Tip: use ./scripts/check_pr_reviews.sh $PR_NUMBER to list unresolved comments."
exit 1
fi
elif [ "$MERGE_STATE" = "BLOCKED" ]; then
Expand Down
Loading