Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
9ccd3bd
fix: require active-author membership for team child spawns (#2213)
ColeMurray Oct 2, 2026
d0a470e
fix(web): keep team pages open after slug renames (#2215)
ColeMurray Oct 2, 2026
452b0b9
fix: start archive preservation before awaiting status projection (#2…
ColeMurray Oct 2, 2026
1c691ec
fix(control-plane): keep a reserved capture while its own deadline ho…
rhlsthrm Oct 2, 2026
864525d
feat: add session source and user attribution analytics (#2218)
ColeMurray Oct 3, 2026
5abc1fb
feat: enforce team boundaries for Slack channels (#2183)
ColeMurray Oct 3, 2026
530d2d0
chore(deps): bump urllib3 from 2.7.0 to 2.8.0 in /packages/sandbox-im…
dependabot[bot] Oct 3, 2026
84a6384
feat(web): autosave session visibility and child-session scope (#2232)
ColeMurray Oct 3, 2026
1a08d57
test: reduce redundant suites while preserving coverage (#2230)
ColeMurray Oct 3, 2026
21eb41f
feat(web): navigate expanded artifacts with arrow keys (#2234)
ColeMurray Oct 3, 2026
29bb988
feat(control-plane): route GitHub work by team and repository ID (#2225)
ColeMurray Oct 3, 2026
006e7c9
fix(web): hide loaded automation list after 403/404 refetch (#2219)
rhlsthrm Oct 3, 2026
87756a1
fix(web): omit unchanged repositories when editing an environment (#2…
rhlsthrm Oct 3, 2026
3b66a3f
feat(web): reorganize analytics into an overview and tabs (#2233)
ColeMurray Oct 3, 2026
026b34d
fix(web): disable wrangler keep_names so next-themes' script runs (#2…
rhlsthrm Oct 3, 2026
aab3d13
fix(control-plane): require sessions.create to create automations (#2…
ColeMurray Oct 3, 2026
34fc771
fix(sandbox-runtime): disable Claude Code file-based auto memory (#2239)
ColeMurray Oct 3, 2026
0dcad19
feat(web): persist unsent prompt drafts across page reloads (#2237)
ColeMurray Oct 3, 2026
0df5f5c
feat(control-plane): add Linear team bindings (#2235)
ColeMurray Oct 3, 2026
2ffffd1
fix(web): persist composer team selection in localStorage (#2240)
ColeMurray Oct 3, 2026
c567c5f
feat: correlate sandbox event processing and acknowledgements (#2211)
ColeMurray Oct 3, 2026
a8fcb88
feat: add classification reasoning effort for OpenAI classifiers (#2243)
ColeMurray Oct 3, 2026
9fd0348
perf: run coverage across parallel CI shards (#2238)
ColeMurray Oct 3, 2026
a146005
feat: add Claude SDK trajectory logging to sandbox runtime (#2210)
ColeMurray Oct 3, 2026
387d33f
fix(web): use shared Select for team channel binding dropdowns (#2245)
ColeMurray Oct 3, 2026
1c02f73
perf: shard control-plane coverage across CI runners (#2247)
ColeMurray Oct 3, 2026
914ad74
feat: diagnose sandbox event loss and heartbeat delays (#2228)
ColeMurray Oct 3, 2026
1fdbd90
fix: harden Terraform plan capture and PR comments (#2246)
ColeMurray Oct 3, 2026
fc516d9
fix(web): keep cached target catalogs through transient errors (#2248)
ColeMurray Oct 3, 2026
2617b02
fix(control-plane): arm heartbeat deadline while awaiting reconnect (…
ColeMurray Oct 3, 2026
420d68a
fix(control-plane): re-authorize automation launches with full guard …
ColeMurray Oct 3, 2026
523d4ef
fix: queue GitHub autofix feedback during recovery holds (#2250)
ColeMurray Oct 3, 2026
d120e46
fix(slack-bot): preserve threads during binding lookup outages (#2252)
ColeMurray Oct 3, 2026
e2f03f2
fix: revoke Slack team-session access on channel unbind (#2253)
ColeMurray Oct 4, 2026
4fb40ec
chore(web): ban native selects and migrate to shared Select (#2256)
ColeMurray Oct 4, 2026
4fd643c
fix: record shadow denials for session lists and WebSockets (#2255)
ColeMurray Oct 4, 2026
d53df53
fix(web): make session visibility saves and cascades reliable (#2254)
ColeMurray Oct 4, 2026
a05d417
feat: add persistent scoped session memory (#2214)
ColeMurray Oct 4, 2026
be36560
docs: update guides for team-owned resources and access (#2217)
ColeMurray Oct 4, 2026
4d98ad1
refactor(web): move personal memory toggle to settings, group sidebar…
ColeMurray Oct 4, 2026
c3c3320
fix: use server capabilities for Teams UI (#2258)
ColeMurray Oct 4, 2026
818a259
docs: consolidate Teams changelog entries (#2262)
ColeMurray Oct 4, 2026
1f9ff86
fix: restrict team session defaults to team or workspace (#2260)
ColeMurray Oct 4, 2026
3abc4be
test(web): fix flaky slack over-limit routing rules timeout (#2264)
ColeMurray Oct 4, 2026
80fd4cc
docs: condense Teams content in authorization guide (#2265)
ColeMurray Oct 4, 2026
d605e35
fix: reply and resolve inline threads in autofix prompt (#2266)
ColeMurray Oct 4, 2026
9bc6a4e
fix(control-plane): resume pending modal-vm lookups on alarm (#2267)
ColeMurray Oct 4, 2026
82aba91
chore(ci): add scoped Dependabot version updates (#2269)
ColeMurray Oct 4, 2026
26649ca
ci: run coverage suites only when their inputs change (#2271)
ColeMurray Oct 4, 2026
9091f57
fix(web): return focus to timeline file links after closing diffs (#2…
ColeMurray Oct 4, 2026
14d0eac
feat(github-bot): support !model and !reasoning flags in mentions (#2…
ColeMurray Oct 4, 2026
556f20a
docs: add changelog entry for GitHub model override flags (#2283)
ColeMurray Oct 4, 2026
d3e533e
chore(deps): bump mypy from 1.19.1 to 2.3.1 in /packages/modal-infra …
dependabot[bot] Oct 4, 2026
2ce49bc
chore(deps-dev): bump lint-staged from 16.4.0 to 17.6.0 (#2276)
dependabot[bot] Oct 4, 2026
7678552
chore(deps-dev): bump @testing-library/jest-dom from 6.9.1 to 7.0.1 (…
dependabot[bot] Oct 4, 2026
954b9f2
chore(deps): bump the actions group across 1 directory with 7 updates…
dependabot[bot] Oct 4, 2026
73d14de
feat(linear-bot): allow choosing the Claude Agent harness for Linear …
ColeMurray Oct 4, 2026
01d1ab5
feat: expose webhook invocation IDs and status endpoint (#2270)
ColeMurray Oct 4, 2026
300f267
fix(types): validate unsafe row casts (#2113)
open-inspect[bot] Oct 4, 2026
b5bb5df
docs: add changelog entries for memory, drafts, and redesigns (#2287)
ColeMurray Oct 4, 2026
31f2afa
fix(web): reset shutdown banner recovery state per phase (#2268)
ColeMurray Oct 4, 2026
8000ed5
fix(docs): align Fumadocs upgrades on one core version (#2288)
ColeMurray Oct 4, 2026
b413e50
chore: upgrade Prettier and apply required formatting (#2289)
ColeMurray Oct 4, 2026
e364a7e
fix(control-plane): re-drive pending prompt after connect timeout (#2…
ColeMurray Oct 4, 2026
2647692
Merge remote-tracking branch 'origin/main' into sync/upstream-2026-10-05
rhlsthrm Oct 5, 2026
b96e260
fix: align merged lockfile and automation policy assertion
rhlsthrm Oct 5, 2026
6ed16b6
fix: rebuild merged lockfile from the fork lock
rhlsthrm Oct 5, 2026
adc1cf8
test: carry merged route snapshots and auto-review label
rhlsthrm Oct 5, 2026
d061f40
fix: hoist vitest 4 for jest-dom and drop unused import
rhlsthrm Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ updates:
- ">= 7.0.0"
# @cloudflare/vitest-pool-workers (0.22.0, the latest) peers on vitest
# ^4.1.0 and runs the control-plane integration tests, so vitest 5 cannot
# run them. Drop this entry once pool-workers accepts vitest 5.
# run them. Drop this entry once pool-workers accepts vitest 5. The
# coverage providers peer on the exact vitest version, so hold them too.
- dependency-name: vitest
versions:
- ">= 5.0.0"
- dependency-name: "@vitest/coverage-*"
versions:
- ">= 5.0.0"
# Prettier tracks the version locked upstream (ColeMurray/background-agents),
# not the latest. A newer formatter rewrites upstream-owned files, which then
# conflict on every sync, and fails format:check on files a sync brings in
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ jobs:
pip install -e ".[dev]"

- name: Run tests
run: pytest tests/ -v
# Dump stacks and exit stalled tests before the job timeout prevents log upload.
run: pytest tests/ -v --durations=20 -o faulthandler_timeout=60 -o faulthandler_exit_on_timeout=true

- name: Run Node.js tests
run: node --test tests/*.test.mjs
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
paths:
- ".env.example"
- ".github/workflows/ci.yml"
- ".github/workflows/terraform.yml"
- ".nvmrc"
- ".prettierignore"
- ".prettierrc"
Expand Down Expand Up @@ -42,6 +43,7 @@ on:
paths:
- ".env.example"
- ".github/workflows/ci.yml"
- ".github/workflows/terraform.yml"
- ".nvmrc"
- ".prettierignore"
- ".prettierrc"
Expand Down
349 changes: 349 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
# Keep the workflow unfiltered so the aggregate Coverage check can be required
# for merge without leaving unrelated pull requests pending. Each suite runs
# only when its inputs change; skipped suites still report success.
name: Coverage

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
changes:
name: Detect coverage inputs
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
control-plane: ${{ steps.changes.outputs.control-plane }}
web: ${{ steps.changes.outputs.web }}
other-ts: ${{ steps.changes.outputs.other-ts }}
python: ${{ steps.changes.outputs.python }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
fetch-depth: 0
- name: Check for changes affecting each coverage suite
id: changes
env:
# PRs compare from the merge base; pushes compare the complete push.
DIFF_RANGE: ${{ github.event_name == 'pull_request' && format('{0}...{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('{0}..{1}', github.event.before, github.sha) }}
run: |
# The coverage gate itself applies to every suite.
gate=(
.github/workflows/coverage.yml
.nvmrc
scripts/check-coverage.mjs
scripts/coverage-baseline.json
scripts/coverage-policy.ts
)
# Every TypeScript suite installs the workspace and imports the built shared package.
typescript=(
package.json
package-lock.json
packages/shared
)
# Inputs outside a suite's own packages are files its tests read or import.
# Keep these lists in sync with those reads. Package READMEs are not inputs.
detect() {
local suite=$1
shift
# Unknown paths and unavailable comparisons run the suite. No rename
# detection: moving an input out of a watched directory must run it.
if git diff --quiet --no-renames "$DIFF_RANGE" -- "$@" ':(exclude,glob)packages/**/*.md'; then
echo "$suite=false" >> "$GITHUB_OUTPUT"
echo "No $suite coverage inputs changed: skipping." >> "$GITHUB_STEP_SUMMARY"
else
echo "$suite=true" >> "$GITHUB_OUTPUT"
fi
}
detect control-plane "${gate[@]}" "${typescript[@]}" \
packages/control-plane \
packages/opencomputer-infra \
packages/sandbox-images \
packages/vercel-infra \
packages/sandbox-runtime/src/sandbox_runtime/runtime_manifest.json \
terraform/d1/migrations \
terraform/environments/production/workers-control-plane.tf \
.env.example \
docker-compose.yml \
docs/CONTROL_PLANE_CONTAINER.md
detect web "${gate[@]}" "${typescript[@]}" \
packages/web \
packages/control-plane/src/session/batch-archive.ts \
packages/control-plane/src/session/contracts.ts \
packages/control-plane/src/session/runtime-client.ts
detect other-ts "${gate[@]}" "${typescript[@]}" \
packages/github-bot \
packages/linear-bot \
packages/slack-bot \
scripts/check-coverage.test.mjs \
docs/AVAILABLE_MODELS.md \
packages/docs/content/docs/models/choosing-a-model.mdx
detect python "${gate[@]}" \
packages/modal-infra \
packages/sandbox-runtime \
packages/sandbox-images \
packages/control-plane/src/image-builds/timeouts.ts \
packages/shared/src/types/integrations.ts \
terraform/modules/modal-app/scripts/deploy.sh

# Control-plane coverage is CPU-bound on per-file Workers pool startup, so it is split across
# runners. Shards skip the full-suite floor; the merge job below enforces it on the merged report.
control-plane-coverage-shard:
name: Coverage (control-plane ${{ matrix.shard }})
needs: changes
if: needs.changes.outputs.control-plane == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: ["1/8", "2/8", "3/8", "4/8", "5/8", "6/8", "7/8", "8/8"]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Install and build shared
run: |
npm ci
npm run build -w @open-inspect/shared
- name: Run control-plane coverage shard
env:
COVERAGE_SHARD: "true"
SHARD: ${{ matrix.shard }}
run: >-
npm run test:coverage -w @open-inspect/control-plane --
--shard="$SHARD"
--reporter=default --reporter=github-actions --reporter=blob
--coverage.reporter=json-summary
- name: Upload shard report
if: always()
uses: actions/upload-artifact@v7
with:
name: control-plane-coverage-shard-${{ strategy.job-index }}
path: packages/control-plane/.vitest-reports/
include-hidden-files: true
if-no-files-found: error

control-plane-coverage:
name: Coverage (control-plane)
needs: control-plane-coverage-shard
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Install dependencies
run: npm ci
- uses: actions/download-artifact@v8
with:
pattern: control-plane-coverage-shard-*
path: packages/control-plane/.vitest-reports/
merge-multiple: true
- name: Merge and enforce control-plane coverage
working-directory: packages/control-plane
run: |
npx vitest run --merge-reports=.vitest-reports --config vitest.coverage.config.ts --coverage
node ../../scripts/check-coverage.mjs control-plane coverage/coverage-summary.json
- name: Upload control-plane coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-data-control-plane
path: packages/control-plane/coverage/
if-no-files-found: error

web-coverage:
name: Coverage (web)
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Install and build shared
run: |
npm ci
npm run build -w @open-inspect/shared
- name: Enforce web coverage
run: |
npm run test:coverage -w @open-inspect/web
node scripts/check-coverage.mjs web packages/web/coverage/coverage-summary.json
- name: Upload web coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-data-web
path: packages/web/coverage/
if-no-files-found: error

other-ts-coverage:
name: Coverage (shared and bots)
needs: changes
if: needs.changes.outputs.other-ts == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Install and build shared
run: |
npm ci
npm run build -w @open-inspect/shared
- name: Test the coverage gate
run: node --test scripts/check-coverage.test.mjs
- name: Enforce TypeScript coverage
run: |
for package in shared slack-bot linear-bot github-bot; do
npm run test:coverage -w "@open-inspect/$package"
node scripts/check-coverage.mjs "$package" "packages/$package/coverage/coverage-summary.json"
done
- name: Upload other TypeScript coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-data-ts
path: |
packages/shared/coverage/
packages/slack-bot/coverage/
packages/linear-bot/coverage/
packages/github-bot/coverage/
if-no-files-found: error

python-coverage:
name: Coverage (Python)
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- uses: astral-sh/setup-uv@v7
with:
version: "0.9.7"
- name: Enforce Python statement and branch coverage
run: |
for package in modal-infra sandbox-runtime; do
uv run --frozen --project "packages/$package" --extra dev pytest "packages/$package/tests" --cov="packages/$package/src" --cov-branch --cov-report="json:packages/$package/coverage/coverage.json"
node scripts/check-coverage.mjs "$package" "packages/$package/coverage/coverage.json"
done
- name: Upload Python coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-data-python
path: |
packages/modal-infra/coverage/
packages/sandbox-runtime/coverage/
if-no-files-found: error

coverage:
name: Coverage
if: ${{ always() }}
needs:
[
changes,
control-plane-coverage-shard,
control-plane-coverage,
web-coverage,
other-ts-coverage,
python-coverage,
]
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Require successful coverage jobs
env:
CHANGES_RESULT: ${{ needs.changes.result }}
CP_CHANGED: ${{ needs.changes.outputs.control-plane }}
WEB_CHANGED: ${{ needs.changes.outputs.web }}
TS_CHANGED: ${{ needs.changes.outputs.other-ts }}
PYTHON_CHANGED: ${{ needs.changes.outputs.python }}
CP_SHARD_RESULT: ${{ needs.control-plane-coverage-shard.result }}
CP_RESULT: ${{ needs.control-plane-coverage.result }}
WEB_RESULT: ${{ needs.web-coverage.result }}
TS_RESULT: ${{ needs.other-ts-coverage.result }}
PYTHON_RESULT: ${{ needs.python-coverage.result }}
run: |
status=0
# A job passes when it succeeded, or was skipped because none of its inputs changed.
require() {
local job=$1 result=$2 changed=$3
if [ "$result" = success ] || { [ "$result" = skipped ] && [ "$changed" = false ]; }; then
return
fi
echo "::error::$job coverage job result: $result"
status=1
}
require changes "$CHANGES_RESULT" true
require control-plane-shards "$CP_SHARD_RESULT" "$CP_CHANGED"
require control-plane "$CP_RESULT" "$CP_CHANGED"
require web "$WEB_RESULT" "$WEB_CHANGED"
require other-ts "$TS_RESULT" "$TS_CHANGED"
require python "$PYTHON_RESULT" "$PYTHON_CHANGED"
exit "$status"
- if: needs.changes.outputs.control-plane == 'true'
uses: actions/download-artifact@v8
with:
name: coverage-data-control-plane
path: packages/control-plane/coverage/
- if: needs.changes.outputs.web == 'true'
uses: actions/download-artifact@v8
with:
name: coverage-data-web
path: packages/web/coverage/
- if: needs.changes.outputs.other-ts == 'true'
uses: actions/download-artifact@v8
with:
name: coverage-data-ts
path: packages/
- if: needs.changes.outputs.python == 'true'
uses: actions/download-artifact@v8
with:
name: coverage-data-python
path: packages/
- name: Upload coverage reports
if: always() && contains(needs.changes.outputs.*, 'true')
uses: actions/upload-artifact@v7
with:
name: production-coverage
path: packages/*/coverage/
if-no-files-found: error
Loading
Loading