Repository navigation
Releases: hami9/runcode
Release list
runcode v1.4.0
Project settings, encrypted secrets and fixes for editor data loss and service lifecycle bugs.
Changes
- Configure environment variables, secret values, ports, restart policy, boot startup and resource limits from each project's settings.
- Store secrets in the Android Keystore-backed vault. Project metadata keeps references; known secret values are masked in logs, crash reports and MCP text results.
- Update plain settings through the new
update_project_settingsMCP tool. Manage secret values in the app. - Preserve unsaved editor content when changing projects, and keep the buffer if saving fails.
- Fix restart retry counts, cancel pending restarts on Stop All, and keep blocked workers in the stopping state until they exit.
- Reject archive entry points outside the source folder and remove imported secret references. Imported secrets must be configured again.
- Use real Telegram polling and HTTP server templates for new projects. Existing scripts stay unchanged.
Install
Download runcode-v1.4.0.apk. Version code is 5, minimum Android version is 8.0 (API 26), and supported ABIs are arm64-v8a and x86_64.
The APK uses the same release certificate as v1.3.0, preserving signing compatibility for updates. Certificate SHA-256: 9e9e346c4a745f9d25c838b04e29221da3258f81b858ec3cb842449e19810568.
Validation
- 32 JVM/Robolectric tests and five Python regression tests pass.
- Android lint passes with no errors; dependency and resource warnings remain.
- Release APK builds and its v2/v3 signatures verify.
SHA256SUMS.txtcontains the checksum for the attached APK.
Known limits
- One Python workload can run at a time because the embedded interpreter shares its environment and working directory. Static web services can still run alongside it. Python scripts and MCP clients remain trusted code with app privileges.
- UI, Android Keystore and upgrade flows have not been tested on a device for this version. A live Telegram round trip has not been tested.
- Exported source and data files are copied as written and may contain secrets placed there by user code; vault contents are excluded.
Changes: #1
runcode v1.3.0 — file manager, zip import/export, editor fixes
Phase 2 of the roadmap: a real file manager for projects, zip import/export, and four new MCP
tools. Testing it on device also turned up a crash and several layout bugs that were already
in the app; those are fixed here too.
File manager
Editor → folder icon. It shows the whole project, not just source/, so whatever a
script writes into data/ is visible.
- Create files and folders (
utils/helpers.pycreates the folder too), rename, move to
another folder, delete — with a confirmation that says how many files a folder holds.
source/,data/,config/,logs/,cache/andbackups/are the project layout and
cannot be renamed or deleted; their contents can. - Renaming or moving the entry point takes the project with it — rename
main.pyand Run
still works. ⋮ → Set as entry point picks another file. - Import files here copies files in from the phone; an existing file is never
overwritten (scores.csv→scores (1).csv). Save a copy to phone gets any file back
out, including databases a script produced. - Binary files and files over 512 KB no longer open in the editor as garbage; the app says
what they are and how to get them out.
Import and export whole projects
⋮ → Export project (.zip) writes code, data and settings. Projects → import icon
brings it back as a new project — verified byte-identical after export → delete → import.
Any other zip works too: a GitHub "Download ZIP" is unwrapped from its top folder, macOS
junk is skipped, and the entry point is detected (main.py, bot.py, app.py, … or
index.html for a website).
Imports are defensive: an archive entry that tries to write outside the project is refused
(tested with ../../../shared_prefs/…; nothing was written), expansion is capped at 512 MB,
and imported projects never start on boot until you turn that on. Exports never contain
secrets — a secret variable is written only as its ${SEC_...} reference.
MCP
Sixteen tools now. New: create_directory, rename_path (rename or move; the entry point
follows), delete_path, and set_entry_point. read_file refuses binary and oversized
files with their size instead of returning mojibake, and list_files covers the whole project
with folders marked by a trailing /.
Edits made over the bridge appear in the app straight away: the tree refreshes, and a file
open in the editor reloads unless you have unsaved changes in it.
Fixed
- Crash when opening a file. Opening a file that added a second tab could crash the app
withIndexOutOfBoundsException: Material3's default tab indicator can recompose with the
new tab index before the new tab is measured. This may be the crash reported earlier. - Line numbers didn't scroll. The numbers stayed put while the code scrolled under them.
The editor now keeps its own scroll position and draws each number at the line's baseline;
long lines wrap and get one number, and the view follows the cursor while you type. - Unsaved edits were lost when switching or closing a tab. They are saved now.
- "New file" with an existing name wiped that file. It now refuses.
- A long project name pushed Run off the editor toolbar; project cards hid their
edit / backup / delete buttons on phone-width screens; the Home New button wrapped
to "Ne / w". - Every path now goes through one containment check that requires a separator, so a sibling
directory sharing a prefix can no longer slip past it.
Install
Signed with the same key as v1.2.0 (certificate SHA-256 9e9e346c…810568), so it installs
over it. versionName is now 1.3.0 — earlier releases all still reported 1.0.0.
minSdk 26, ABIs arm64-v8a and x86_64. Licensed MIT; third-party notices in
THIRD_PARTY_LICENSES.md.
runcode v1.2.0 — Python 10x faster, no more mixed-up output
Testing the runtime properly on device turned up four defects, all fixed here, plus the
first phase of resource monitoring.
Python was 25x slower than it should have been
run_script installed a sys.settrace hook so a running script could be interrupted — and
that hook fires on every line. Same loop, same process, measured on device:
WITH tracer 0.945s -> 317,478 iter/s
WITHOUT tracer 0.037s -> 8,134,909 iter/s
Stopping now uses PyThreadState_SetAsyncExc through ctypes: it raises in the target thread
at its next bytecode boundary and costs nothing while the script runs normally. A script
blocked in time.sleep(1) still stops 0.8 s after you press Stop.
Real-world effect: a plain arithmetic loop went from 317k to 1.2–4.6M iterations/second.
Two services stole each other's output
sys.stdout is process-global and each script replaced it, so whichever started last
captured both. With a Python project and a Telegram bot running together, A tick 11 showed
up in the bot's log. Output is routed per thread now — verified with two concurrent services,
each log containing only its own lines.
The log file was out of order
A coroutine was launched per line, so lines interleaved on the way to disk while the
in-memory view stayed correct. One consumer drains a queue instead. Twenty lines printed in
sequence now land in sequence.
A broken bridge triggered a restart loop
If the Python bridge module itself failed to load, that was classified as a crash, so
ON_FAILURE projects kept retrying something that could never work. It is fatal now.
Resource monitoring and limits
The app used to report cpuPercent = 2 for Python and 1 for the web server — both
hardcoded. Now:
- CPU is sampled per service from
/proc/self/task/<tid>/stat, differenced between
heartbeats. Every service already runs on its own named thread, so the attribution is real. - Memory comes from
Debug.MemoryInfo; the System screen shows PSS, Java heap against the
device's limit, thread count and pid. - Projects can set CPU %, heap MB and idle minutes limits (blank = unlimited).
A sustained breach marks the serviceDEGRADED, then stops it:
Stopping 'Python Quickstart': CPU 99% over the 20% limit for 9s.
This is runcode policing its own services. An Android app cannot free another app's memory or
tune the CPU — there is no API for it — and nothing here pretends otherwise.
Install
Signed with the same key as v1.0.x, so it installs over it. minSdk 26, ABIs arm64-v8a and
x86_64. Licensed MIT; third-party notices in THIRD_PARTY_LICENSES.md.
runcode v1.0.1 — real Telegram bots, no more restart flapping
Everything here came out of testing v1.0.0 on a real phone.
Third-party Python packages
A real bot script used to die on from telegram import Update no matter what, because only
the standard library shipped. pip now resolves packages at build time and they are packaged
into the APK: python-telegram-bot 21.9 and requests.
Verified on device — the library loads, reaches api.telegram.org over the bundled OpenSSL,
and the server answers:
python-telegram-bot version: 21.9
server replied: InvalidToken - The token `1111...:***REDACTED***` was rejected by the server.
(That redaction is the log sanitiser doing its job on a real bot token.)
Add more packages to the pip block in app/build.gradle.kts. pip runs on the build
machine, so building now needs a CPython 3.12 — set RUNCODE_BUILD_PYTHON if it is not on
PATH.
Fixes
- A syntax error made the service flap. The Telegram profile restarts
ON_FAILURE, so
the supervisor retried aSyntaxErrorfour times with backoff even though it fails
identically every time. Scripts that cannot load at all —SyntaxError,
ModuleNotFoundError— are now reported as fatal and never retried. - Battery optimisation state was stale. It was read once at startup, so granting the
exemption in Settings and coming back still showed "OPTIMIZED". Re-read on every resume. - Memory was printed raw —
RAM: 17.93731689453125MB— which also overflowed the Home
card and squeezed its Stop button down to a bare icon. Formatted to one decimal. - Crashes are now readable. Android's dialog says only "Something went wrong" and the
trace is only reachable over adb, which is no use on a phone. An uncaught-exception
handler persists the stack trace and the System screen shows it with a copy button.
Install
Signed with the same 4096-bit RSA key as v1.0.0 (v2 + v3 schemes), so it installs over it.
minSdk 26, ABIs arm64-v8a and x86_64.
Licensed MIT. Third-party notices: THIRD_PARTY_LICENSES.md.
runcode v1.0.0 — real CPython, working web server, shell and MCP bridge
runcode v1.0.0
An on-device Android runtime: real CPython, a working local web server, a shell, and an
authenticated MCP bridge so an AI client can drive the whole thing.
Tested on Android 16 (API 36). minSdk 26.
Highlights
Real Python. CPython 3.12 is embedded via Chaquopy. Scripts run as __main__ with the
project directory as the working directory, so relative paths like data/tasks.sqlite
resolve the way they do on a desktop. Output streams into the console line by line while the
script runs, and a long-running loop can be interrupted from the UI or over MCP.
A local web server that works. Static web projects are served by a built-in HTTP/1.1
server with correct content types, SPA fallback and directory-escape protection.
A shell. Interactive /system/bin/sh inside the app sandbox, with the selected project's
directory as cwd and its environment injected. No root, no PTY, no job control — it is the
shell every Android device already ships.
MCP bridge. JSON-RPC 2.0 over POST /mcp, protocol revision 2025-06-18, twelve tools:
list_projects · get_project · list_files · read_file · write_file ·
start_service · stop_service · service_status · get_logs · run_command ·
run_python · sql_query
Point any MCP client at it:
adb forward tcp:8765 tcp:8765
curl -s -X POST http://127.0.0.1:8765/mcp \
-H "Authorization: Bearer $RUNCODE_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Security
The bridge exposes a shell, arbitrary Python and read/write file access, so:
- a bearer token is required on every call including on loopback, compared in constant time;
- the token is 24 random bytes kept in an Android Keystore-backed vault and rotatable from
the UI; - the listener binds
127.0.0.1unless LAN exposure is explicitly enabled; - secret-valued environment variables are returned as
<secret>, never echoed; GET /healthis the only unauthenticated route and reveals nothing about the device;- the process is held in the foreground while the bridge is online.
The secret vault itself now uses an Android Keystore key with a random IV per value, and
refuses to store a value it cannot encrypt. Backup rules no longer ship the vault to cloud
backup.
Backup archives are checksum-verified for real. The screen always claimed they were;
nothing was ever compared. Both Verify and Restore now refuse an archive whose payload does
not hash to the value in its manifest.
Fixes
- A fresh clone could not build: the signing config pointed at a
debug.keystorethat was
not in the repo. There is now a Gradle wrapper and a.gitignoreas well. - Static web projects crashed the app with
NoClassDefFoundErroron
com.sun.net.httpserver, which exists in the JDK but not on Android. - Every log line crashed the app on Android 8–13:
Matcher.replaceAll(Function)is API 34+
and was called on each line withminSdk 26.FOREGROUND_SERVICE_TYPE_SPECIAL_USEhad the
same problem. - A script that finished successfully was reported as
FAILED. - A port conflict picked a free port, logged it, then bound the occupied one anyway.
- The wake lock was re-acquired on every automatic restart and never released.
- Environment variables, arguments and the working directory were not persisted, so a
configured Telegram bot token vanished on restart. - The bottom navigation could not fit its destinations and wrapped labels mid-word.
- On the Backups screen the Restore button was pushed off the right edge by long archive
names and could not be tapped, and a failed verification showed no reason.
Android lint: 11 errors → 0.
Known limitations
pippackages are not enabled; third-party libraries need a build-time requirements list
in thechaquopy { }block.- JavaScript and PHP runtimes are not implemented and are labelled as such in the UI.
- The MCP bridge lives in the app process and does not survive the process being killed.
- The APK is ~52 MB because CPython ships for
arm64-v8aandx86_64. Trimming
abiFiltersto a single ABI roughly halves it.
Install
runcode-v1.0.0.apk is signed with a 4096-bit RSA key (APK Signature Scheme v2 + v3),
minSdk 26, ABIs arm64-v8a and x86_64. Sideload it — this is not on any store, so your
device will ask you to allow installation from an unknown source.
Licensed MIT.