Files
simplex-chat/scripts/ci/README.md
T
Evgeny @ SimpleX Chat be668564df mcp server: files, /leave and library methods (#7962)
* bots, nodejs: document chat, connection, member contact and network commands, typed commands in library

* bots, nodejs: restore apiConnect, wrap domain once, test all connect responses

* nodejs: JSDoc for new methods

* docs: plan for bot API commands and typed Node library methods

* tests: channel prepared with a domain has a verified SimpleX name

* docs: apply review fixes to the plan for bot API commands

* docs: apply second review pass to the plan for bot API commands

* docs: refer the bot API plan to the rewritten test commit

* docs: record decisions in the plan for bot API commands

* docs: plan tests of the generated Python command strings

* python: test the generated strings of the documented commands

* docs: plan for files, /leave and library methods in the MCP server

* docs: wait at stop in the MCP plan

* docs: library methods and /leave mechanics in the MCP plan

* mcp: typed library methods for chat commands

* docs: plan the message information command for bots

* mcp: /leave CHAT owner command to leave a chat at once

* docs: file servers, GIF and video choices decided in the MCP plan

* docs: file mechanics in the MCP plan

* mcp: files of received messages

* bots, nodejs: message information with the file servers for bots

* docs: plan the relay approval option of receiving files in Node

* mcp: download of received files with owner approval

* docs: name the xftp-server test helper in the files plan

* nodejs: receive files without approving unknown relays

* docs: plan the user servers command for bots

* bots, nodejs: user servers and relays by operator for bots

* nodejs: document the message information method

* docs: plan the file upload progress event for bots

* mcp: send files with owner approval, refusing private keys

* bots: file upload progress event for bots

* mcp: /leave template in the agent control chat menu

* docs: sent copies are deleted for contacts too, at sndFileCompleteXFTP

* bots: drop the file upload progress event

* docs: drop the upload progress event plan

* nodejs: document user servers and relay approval of received files

* mcp: download cards name unknown file servers after the upload

* docs: record the wait for the file description in the files plan

* mcp: fixed wait at stop for cancelled cards, without /sql

* docs: record decision C4, a fixed wait at stop, in the files plan

* ci: test the MCP server package on pull requests

* ci: state when the MCP workflow can be dispatched

* ci: run the MCP server tests when its libraries change

* mcp: log the errors of image previews and resizing

* mcp: keep files in transfer in the data folder

* mcp: restrict sent files by the real paths of refused folders

* mcp: validate the file at the send by its inode and change time

* mcp: limit ffmpeg and ffprobe to local files and no input commands

* mcp: ask the owner for unknown file servers; read approvals at creation

* mcp: name downloaded files by their ID; return paths of downloads only

* mcp: end each download with a result for the agent and the card

* mcp: show files above their sender's size limit as failed

* mcp: refuse empty files, as without file and captions too long to send

* mcp: tell the owner about failed uploads and unposted received files

* mcp: delete resized copies left by the last run at start

* mcp: name downloads and leaving in the help and greeting, align docs

* mcp: compute the download path for received files only

* mcp: send a /receive file to the owner under its name

* mcp: keep files received without a download in the chat's folder

* mcp: keep a left chat's files and delete them with /delete CHAT files

* mcp: test that sent images are resized by the core

* mcp: wait 30 minutes for a download's progress before cancelling it

* mcp: end a download as deleted only when its message is not found

* mcp: delete the copy of a /receive file whose post fails

* mcp: count the quoted content in a message's length, as the core sends it

* mcp: name received bytes, unknown-server downloads and left chat files plainly

* mcp: test refused folders, file changes and download approvals

* docs: align MCP server docs with refused folders, leaves and download waits

* mcp: move received files only in full, delete partials only of downloads

* mcp: report a received file missing from its folder as failed

* mcp: move received files of a group's member chats into its folder

* mcp: read a video's preview and duration as an MP4 or QuickTime file

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.1

* mcp: file folders by chat type, /delete cancels downloads, files reply

* docs: MCP server specs and plan for 0.2.0

* mcp: /receive gives the agent a file, /forward sends it to the owner

* docs: MCP server specs for /receive and /forward of download cards

* docs: MCP plan records the temp folder command as a library exception

* mcp: /delete cancels resumed downloads; /forward quote fits the limit

* docs: MCP specs and plans for /forward quotes and resumed downloads

* docs: MCP specs name the source commit they are checked against

* docs: MCP PLAN states the /forward quote and move rules

* mcp: a download moves its completed file whatever its size

* mcp: /receive N all covers its card and later files, not pending ones

* docs: MCP files approval scope, reply text and README server wording

* mcp: a download approves relays only for a card naming unknown servers

* mcp: files in member chats of a group are tracked as in the main chat

* docs: MCP relay approval of downloads and files in member chats

* mcp: a rejected file gets a card again under the files approval

* docs: MCP rejected files under the files approval, README scope

* mcp: an offered file without a description gets no card or action

* docs: MCP a file without a description is not downloaded

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.2

* ci: test the MCP server as a scope of build-linux
2026-11-06 18:38:34 +00:00

12 KiB
Raw Blame History

CI on GitHub's runners

Three workflows build the apps from a branch's own code and test them, only when asked: ios-sim (the iOS app in a simulator on a macOS runner), desktop-linux (the desktop app on a Linux runner) and android-emu (the Android app in an emulator on a Linux runner). Each runs a scripted smoke test and, on request, a Claude session that follows a test plan, and a test backend (relay, directory, names server, peers). Details: iOS, desktop, Android.

Reduced build

GitHub runs the reduced build of build.yml on a dispatch (workflow_dispatch), on each push to a draft pull request and on a push to master or stable in a fork. Once the pull request is marked ready for review, GitHub runs the full matrix, as on a push to master or stable in simplex-chat/simplex-chat.

On a push to master or stable in a fork, the release steps and tests are skipped; build-linux saves the -O0 build caches, and on master the Docker image cache, that the fork's dispatches restore.

On a draft pull request, the tests are selected by a fenced block with the info string ci-tests in the description, one hspec match pattern per line; the block is read at each push. / runs the whole suite; without a block, the reduced build runs no tests.

Dispatch

curl -fsS -X POST \
  -H "Authorization: Bearer $GH_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/<owner>/simplex-chat/actions/workflows/build.yml/dispatches \
  -d '{"ref": "<branch>", "inputs": {"tests": "/SimpleX chat client/group tests/\n/Message batching/"}}'

Endpoint: https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event. Inputs:

Input Default Value
tests empty hspec match patterns, one per line (\n in JSON); / runs the whole suite, and an empty value builds without tests
base master the branch that the changes are compared with:
stable for a branch cut from stable
the parent branch for a stacked branch

When a newer dispatch on the same branch starts build-linux or build-ios, GitHub cancels the unfinished job of the older one (cancel-in-progress).

Results:

  • Run and jobs: GET /repos/<owner>/simplex-chat/actions/runs?head_sha=<commit>&event=workflow_dispatch returns the run id; GET /repos/<owner>/simplex-chat/actions/runs/<run id>/jobs returns each step with its start, end and conclusion.
  • Job log: GET /repos/<owner>/simplex-chat/actions/jobs/<job id>/logs returns a redirect to a text file, valid for 1 minute (https://docs.github.com/en/rest/actions/workflow-jobs#download-job-logs-for-a-workflow-run). The test summary is printed as "Finished in N seconds" and "N examples, N failures".

Scopes

The reduced build runs one Linux job, build-linux (ubuntu-22.04 x86_64), for every scope except ios, and one macOS job, build-ios (macos-latest), for ios.

core

  • Globs: BUILD_CHANGES["haskell"] in packages_release.py.
  • CI, in build-linux:
    • Docker image (step "Build and cache Docker image");
    • caches (the steps "Restore ... from cache");
    • cabal build -j --enable-tests --ghc-options=-O0 (step "Build CLI");
    • simplex-chat-test with the selected tests (step "Run tests"), each attempt limited to 35 minutes by timeout; an attempt that reaches the limit fails, and "Attempt timed out." is printed.
  • Local:
    cp scripts/cabal.project.local.linux cabal.project.local
    cabal build --enable-tests --ghc-options=-O0
    mkdir -p tests/tmp
    cabal test simplex-chat-test --ghc-options=-O0 --test-option=-m --test-option='/SimpleX chat client/group tests/'
    
    The selection runs as in CI with scripts/ci/run-tests.sh, which appends the patterns of TESTS to the test binary:
    TESTS='/SimpleX chat client/group tests/' scripts/ci/run-tests.sh "$(cabal list-bin simplex-chat-test --ghc-options=-O0)"
    
    --ghc-options=-O0 matches the reduced build; the library and test-suite stanzas of simplex-chat.cabal set -O2. Test directories are created under tests/tmp (tmpBracket in tests/Test.hs).

kotlin

  • Globs: BUILD_CHANGES["kotlin"] in packages_release.py.
  • CI: Gradle in the Docker container of build-linux (step "Build and test Kotlin apps", matrix field kotlin).
  • Local:
    cd apps/multiplatform
    ./gradlew --no-daemon --max-workers=1 :common:desktopTest :desktop:compileKotlinJvm -x :desktop:cmakeBuildAndCopy :android:compileGoogleDebugKotlin :android:testFossDebugUnitTest
    
    JDK 17 and an Android SDK in ANDROID_HOME are required, as installed by Dockerfile.build.

ios

  • Globs:
    • apps/ios/**
    • scripts/ci/ios-stub-libs.sh
  • CI: build-ios: stub libraries, then xcodebuild for the iOS Simulator without signing.
  • Local, on macOS: scripts/ci/ios-stub-libs.sh, then the xcodebuild command of the step "Build iOS app" of build-ios.

packages

  • Globs:
    • packages/simplex-chat-nodejs/**
    • packages/simplex-chat-client/types/typescript/**
    • packages/simplex-chat-python/**
    • scripts/ci/package-tests.sh
  • CI, in build-linux:
    • Node 22 and Python 3.12 (steps "Setup Node.js" and "Setup Python");
    • libsimplex, when CORE_SOURCES of packages_release.py also changed (step "Build library");
    • package-tests.sh build linux-x86_64 and package-tests.sh test (step "Package tests (pull request)").
  • With libsimplex built, SIMPLEX_LIBS_DIR is set to it for the nodejs tests; the python tests run with it unset (package-tests.sh). Without SIMPLEX_LIBS_DIR, the pinned release libraries are downloaded (packages/simplex-chat-nodejs/src/libs.ts).
  • Local:
    scripts/ci/package-tests.sh build linux-x86_64
    scripts/ci/package-tests.sh test
    

badge-web

  • Globs: apps/simplex-badge-service/web/. These paths are also in the core globs (apps/simplex-badge-service/), and web/src/catalog.ts is read by core tests (tests/Bots/BadgeService/CatalogTests.hs, WebTests.hs).
  • CI, in build-linux: core build and tests, then scope-tests.sh badge-web (step "Test changed scopes").
  • Selected core tests: /SimpleX badge service/ and /SimpleX badge service e2e/.
  • Local:
    cd apps/simplex-badge-service/web
    npm ci
    npm test
    

ci-scripts

  • Globs:
    • scripts/ci/packages_release.py
    • scripts/ci/test_packages_release.py
  • CI, in build-linux: scope-tests.sh ci-scripts (step "Test changed scopes").
  • Local: python3 scripts/ci/test_packages_release.py; Python 3.11 or newer is required for tomllib (packages_release.py).

website

  • Globs:
    • website/src/js/simplex-lib.jsc
    • website/test/**
    • website/package.json
  • CI:
    • in build-linux: scope-tests.sh website (step "Test changed scopes");
    • on master pushes: build and deploy of website/, images/, blog/, docs/ and PRIVACY.md (web.yml).
  • Local:
    cd website
    npm test
    

mcp

  • Globs:
    • packages/simplex-chat-mcp/**
    • packages/simplex-chat-nodejs/**
    • packages/simplex-chat-client/types/typescript/**
  • CI:
    • in build-linux: scope-tests.sh mcp (step "Test changed scopes"):
      • the types package and the Node.js library built from the branch without the add-on, as package-tests.sh builds them;
      • npm ci, then both installed into the MCP package with npm install --no-save;
      • npx tsc and npx tsc -p test/tsconfig.json --noEmit;
      • npm test, the unit tests;
      • npm run test:e2e, the end-to-end tests. They download libsimplex and the add-on pinned in packages/simplex-chat-nodejs/src/release.ts, and smp-server and xftp-server of the simplexmq release in test/e2e/smpServer.ts.
  • Local: scripts/ci/scope-tests.sh mcp.

Scopes lacking CI tests

Scope Paths CI
ios apps/ios/** compile only
types package packages/simplex-chat-client/types/typescript/** compile only
ts-client packages/simplex-chat-client/typescript/** -
webrtc packages/simplex-chat-webrtc/** -
calculator-bot apps/simplex-calculator-bot/** -
support-bot apps/simplex-support-bot/** -
support-bot-light apps/simplex-support-bot-light/** -
prices scripts/prices/** -
nft eth/nft/** -

CI coverage of such a scope is added in the pull request that changes it (Adding CI coverage).

Adding CI coverage

For a scope lacking CI tests, in the pull request that changes it:

  1. scripts/ci/packages_release.py: an entry "<scope>": ["<glob>", ...] in SCOPE_CHANGES.
  2. build.yml, on.pull_request.paths: the same globs; the two are compared in test_build_yml_runs_for_the_paths_of_its_builds of test_packages_release.py.
  3. scripts/ci/scope-tests.sh: a <scope>) case with the build and test commands, and the scope name in the usage line.
  4. scripts/ci/test_packages_release.py, ChangesTest: a case asserting that the scope is printed in scopes for a change to one of its files.
  5. This file, "Scopes": a subsection for the scope, in place of its row in "Scopes lacking CI tests".
  6. Checks: python3 scripts/ci/test_packages_release.py, the new case of scope-tests.sh locally, then a dispatch of the reduced build.

The scope commands run in build-linux after the core tests, with Node 22 and Python 3.12 (steps "Setup Node.js" and "Setup Python"). For a scope that needs other versions, a setup step is added before "Test changed scopes", conditioned on the scope name in needs.maybe-release.outputs.scopes. A scope that needs macOS is a BUILD_CHANGES entry with a separate maybe-release output, as ios is, and a step in build-ios; the if of build-ios includes that output.

Starting a run

Pushes start none of the app workflows: a run is started by hand, on any branch, with inputs that are off by default and combine.

Input iOS Desktop Android
no input scripted test, the app uploaded scripted test, the app uploaded scripted test, the app uploaded
claude scripted test, then Claude scripted test, then Claude scripted test, then Claude
backend also the test backend also the test backend also the test backend
release — also the latest release's onboarding, to tell a branch's failure from the runner's also the latest release's onboarding, to tell a branch's failure from the runner's
armv7a — — also the core library for 32-bit Arm (armeabi-v7a), built only

In the Actions tab: ios-sim, desktop-linux or android-emu → Run workflow, choose the branch and tick the inputs. With the GitHub CLI, each input is a field:

gh workflow run ios-sim.yml --ref <branch>
gh workflow run ios-sim.yml --ref <branch> -f claude=true
gh workflow run desktop-linux.yml --ref <branch> -f claude=true -f backend=true -f release=true
gh workflow run android-emu.yml --ref <branch> -f claude=true

A newer run of a workflow on the same branch cancels the older one.

Each run uploads the app it built, for manual tests, as an artifact kept 14 days: ios-sim-app (SimpleX.app.tar.gz, for the iOS simulator on an Apple-silicon Mac), desktop-app (desktop-app.tar.gz, for Linux x86-64) and android-app (the debug APK, arm64-v8a only). The run's page lists it under Artifacts; gh run download <run id> -n <artifact> downloads it.

How long a run takes

Run iOS Desktop Android
Scripted test, builds reused about 25 min, plus the wait for a macOS runner, which the team's builds share (minutes to an hour) about 10 min about 15 min
The core rebuilt, after Haskell changes 30–45 min more about 25 min more about 27 min more
With the test backend 20–45 min more when it is rebuilt, then about 10 min to start about 30 min more when it is rebuilt, then a few minutes to start as desktop, whose backend it shares
With Claude on the smoke plan 10–15 min more about 4 min more about 5 min more

The Claude job

It needs the GitHub environment app-sim-claude with the secret CLAUDE_CODE_OAUTH_TOKEN, a Claude subscription token. The environment's branch rule alone decides which branches may use it: on a branch it doesn't allow, a run that asks for Claude fails at the Claude job's environment check, and the scripted test runs as usual. Each run spends the subscription's usage limits; the job summary gives its tokens and cost estimated at API prices.