Sitelet https://github.com/NodeOps-app/createos-sandbox-sdk/commit/2988de6725fe6a7c9b7114b366d369e328fe7c46
Skip to content

Commit 2988de6

Browse files
committed
ci(examples): replace matrix with parallel xargs (5 workers)
Run all examples in a single job using `xargs -n1 -P5` instead of a matrix. Logs per example are written to `/tmp/example-logs/` and failure markers to `/tmp/example-fails/` for aggregation. The results step always runs, writing a pass/fail table to the GitHub Actions step summary and outputting full logs for failed examples via `::group::` blocks. Timeout increased to 90 minutes to allow sequential waves.
1 parent a0fae01 commit 2988de6

1 file changed

Lines changed: 75 additions & 63 deletions

File tree

‎.github/workflows/examples.yml‎

Lines changed: 75 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,31 @@ jobs:
8787
run:
8888
needs: discover
8989
runs-on: arc-runner-set
90-
timeout-minutes: 20
91-
strategy:
92-
# No max-parallel: intentionally run every example at once. This doubles
93-
# as a load test of the createos-sandbox control plane — surfacing its
94-
# behavior under a full parallel burst (503s, pause/resume latency, /exec
95-
# blips) is a signal we want, not noise to suppress. Add a cap only if the
96-
# goal shifts back to deterministic green CI.
97-
fail-fast: false # one failing example must not cancel the others
98-
matrix:
99-
example: ${{ fromJson(needs.discover.outputs.examples) }}
100-
name: ${{ matrix.example }}
90+
# 5 concurrent waves × 20 min per example = up to 90 min worst-case
91+
timeout-minutes: 90
92+
env:
93+
EXAMPLES_JSON: ${{ needs.discover.outputs.examples }}
94+
# createos-sandbox control plane (read by the SDK + most examples)
95+
CREATEOS_SANDBOX_API_KEY: ${{ secrets.CREATEOS_SANDBOX_API_KEY }}
96+
CREATEOS_SANDBOX_BASE_URL: ${{ secrets.CREATEOS_SANDBOX_BASE_URL }}
97+
# OpenAI / OpenAI-compatible
98+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
99+
OPENAI_API_URL: ${{ secrets.OPENAI_API_URL }}
100+
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
101+
OPENAI_MODEL: ${{ secrets.OPENAI_MODEL }}
102+
LLM_MODEL: ${{ secrets.LLM_MODEL }}
103+
# Anthropic
104+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
105+
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
106+
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
107+
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL }}
108+
# S3-compatible bucket (example 38)
109+
S3_BUCKET: ${{ secrets.S3_BUCKET }}
110+
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
111+
S3_REGION: ${{ secrets.S3_REGION }}
112+
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
113+
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
114+
S3_USE_PATH_STYLE: ${{ secrets.S3_USE_PATH_STYLE }}
101115
steps:
102116
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
103117

@@ -133,65 +147,63 @@ jobs:
133147
working-directory: examples
134148
run: bun install
135149

136-
# example 20 creates a Python venv on first run; setup-python ensures
137-
# venv support is present (plain python3 on Ubuntu lacks python3-venv).
138-
- name: Set up Python (google-adk example)
139-
if: matrix.example == '20-google-adk-agent'
150+
# example 20 needs python3-venv; install once since the job runs all examples
151+
- name: Set up Python
140152
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
141153
with:
142154
python-version: "3.12"
143155

144-
# example 38 mounts an S3 bucket as a disk; the script seeds + verifies
145-
# objects but never creates the bucket. Backed by play.min.io, which
146-
# purges buckets periodically — (re)create it before each run so a wipe
147-
# between nightly runs can't fail the job on a missing bucket.
148-
- name: Ensure S3 bucket (minio play, example 38)
149-
if: matrix.example == '38-s3-disk-ffmpeg-transcode'
150-
env:
151-
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
152-
S3_BUCKET: ${{ secrets.S3_BUCKET }}
153-
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
154-
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
156+
# example 38 mounts an S3 bucket; play.min.io purges buckets periodically
157+
- name: Ensure S3 bucket (example 38)
155158
run: |
156159
curl -sSfL https://dl.min.io/client/mc/release/linux-amd64/mc -o /tmp/mc
157160
chmod +x /tmp/mc
158161
/tmp/mc alias set ci "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY"
159162
/tmp/mc mb --ignore-existing "ci/$S3_BUCKET"
160163
161-
- name: Run ${{ matrix.example }}
162-
working-directory: examples/${{ matrix.example }}
163-
env:
164-
# --- createos-sandbox control plane (read by the SDK + most examples) ---
165-
CREATEOS_SANDBOX_API_KEY: ${{ secrets.CREATEOS_SANDBOX_API_KEY }}
166-
CREATEOS_SANDBOX_BASE_URL: ${{ secrets.CREATEOS_SANDBOX_BASE_URL }}
167-
# --- OpenAI / OpenAI-compatible ---
168-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
169-
OPENAI_API_URL: ${{ secrets.OPENAI_API_URL }}
170-
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
171-
OPENAI_MODEL: ${{ secrets.OPENAI_MODEL }}
172-
# OPENAI_USE_CHAT_COMPLETIONS: ${{ secrets.OPENAI_USE_CHAT_COMPLETIONS }}
173-
# OPENAI_AGENTS_DISABLE_TRACING: ${{ secrets.OPENAI_AGENTS_DISABLE_TRACING }}
174-
LLM_MODEL: ${{ secrets.LLM_MODEL }}
175-
# --- Anthropic ---
176-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
177-
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
178-
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
179-
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL }}
180-
# --- third-party integrations (per-example) ---
181-
# BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
182-
# BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
183-
# FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
184-
# FIRECRAWL_API_URL: ${{ secrets.FIRECRAWL_API_URL }}
185-
# OPENCLAW_GATEWAY_TOKEN: ${{ secrets.OPENCLAW_GATEWAY_TOKEN }}
186-
# SCRAPE_URL: ${{ secrets.SCRAPE_URL }}
187-
# --- S3-compatible bucket for example 38 (s3-disk-ffmpeg-transcode) ---
188-
# Backed by the public MinIO Playground (play.min.io). The bucket is
189-
# (re)created by the "Ensure S3 bucket" step above, since play purges
190-
# buckets periodically and the example does not create one itself.
191-
S3_BUCKET: ${{ secrets.S3_BUCKET }}
192-
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
193-
S3_REGION: ${{ secrets.S3_REGION }}
194-
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
195-
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
196-
S3_USE_PATH_STYLE: ${{ secrets.S3_USE_PATH_STYLE }}
197-
run: bun index.ts
164+
- name: Run examples (parallel, max 5)
165+
run: |
166+
mkdir -p /tmp/example-logs /tmp/example-fails
167+
mapfile -t EXAMPLES < <(echo "$EXAMPLES_JSON" | jq -r '.[]')
168+
169+
printf '%s\n' "${EXAMPLES[@]}" | xargs -n1 -P5 bash -c '
170+
example="$1"
171+
log="/tmp/example-logs/$example.log"
172+
(cd "$GITHUB_WORKSPACE/examples/$example" && timeout 1200 bun index.ts) >"$log" 2>&1
173+
rc=$?
174+
[[ $rc -ne 0 ]] && echo "$rc" > "/tmp/example-fails/$example"
175+
printf "[%s] %s\n" "$([ $rc -eq 0 ] && echo "OK " || echo "FAIL")" "$example"
176+
exit $rc
177+
' _ || true
178+
179+
- name: Report results
180+
if: always()
181+
run: |
182+
mapfile -t EXAMPLES < <(echo "$EXAMPLES_JSON" | jq -r '.[]')
183+
mapfile -t FAILED < <(ls /tmp/example-fails/ 2>/dev/null | sort)
184+
total=${#EXAMPLES[@]}
185+
nfailed=${#FAILED[@]}
186+
187+
{
188+
echo "## Examples: $(( total - nfailed ))/${total} passed"
189+
echo ""
190+
echo "| Example | Result |"
191+
echo "| --- | --- |"
192+
for ex in "${EXAMPLES[@]}"; do
193+
if [[ -f "/tmp/example-fails/$ex" ]]; then
194+
echo "| \`$ex\` | ❌ |"
195+
else
196+
echo "| \`$ex\` | ✅ |"
197+
fi
198+
done
199+
} >> "$GITHUB_STEP_SUMMARY"
200+
201+
if [[ $nfailed -gt 0 ]]; then
202+
echo "::error::${nfailed} of ${total} example(s) failed: ${FAILED[*]}"
203+
for ex in "${FAILED[@]}"; do
204+
echo "::group::FAILED: $ex"
205+
cat "/tmp/example-logs/$ex.log" || true
206+
echo "::endgroup::"
207+
done
208+
exit 1
209+
fi

0 commit comments

Comments
 (0)