Reference
Limits
Every limit Portal applies, with its value and where it is set in Portal's code.
Warnings and limits
- Your providers' quotas and capacity are limits too, and often the first you meet: Portal does not raise them. See Quotas and capacity.
- A request over a limit is refused with a sentence that names it. Rate limits answer
429withRetry-After. - Sources are files under
src/portal/in Portal's code, with the constant or function that sets the value. A test checks that each one exists. - Portal is open to anyone with an active Clusy account and a verified email address.
- The limits that decide whether a workload goes through, in words, are on Known limits.
Access and requests
| Limit | Value | Source |
|---|---|---|
| Who is admitted | Portal is open to anyone with an active Clusy account and a verified email address. | platform/api/config.py ADMIT_POLICIES |
| Console session | ends after 12 hours unused, 7 days at most | gateway/sessions.py SESSION_IDLE_S, gateway/directory.py SESSION_MAX_S |
| Fresh sign-in for sensitive actions | from the last 5 minutes | gateway/sessions.py FRESH_S |
| Device sign-in code | lasts 10 minutes | service/auth.py DEVICE_TTL_S |
| CLI credential | renews every 15 minutes, lasts until revoked | gateway/cli_auth.py ACCESS_S |
| Agent token | 1, 7 or 30 days | gateway/tokens.py EXPIRY_DAYS |
| Sign-in starts | 30 a minute per client | gateway/signin.py STARTS_PER_MINUTE |
| Requests in flight | 128 per workspace, then 429 | gateway/proxy.py WORKSPACE_REQUESTS_MAX |
| Request body | 1 MiB | gateway/policy.py BODY_MAX |
| Request body for a secret, a connection or settings | 4 MiB | gateway/policy.py LARGE_BODY_MAX |
| A job's form | 8 MiB (the project goes by upload link) | gateway/policy.py UPLOAD_BODY_MAX |
| A log read's wait | 10 s at most | gateway/policy.py WAIT_MAX_S |
| Installer downloads | 30 requests a minute per client | service/install.py DOWNLOADS_PER_MINUTE |
Connections
| Limit | Value | Source |
|---|---|---|
| Connections per workspace | 24 | service/connections.py MAX_CONNECTIONS |
| Connections per cloud | 8 | service/connections.py MAX_PER_KIND |
| A connection's label | 1 to 24 lowercase letters, digits or hyphens | providers/names.py LABEL |
| Verification when saved | 60 s | service/connections.py VERIFY_TIMEOUT_S |
| An AWS ExternalId waiting to be used | 24 hours | service/connections.py EXTERNAL_ID_TTL_S |
| A secret's name | up to 128 characters | service/connections.py SECRET_NAME |
| Secrets per workspace | 64, and 16 MiB sealed in all; past either, storing one more is refused (409) | service/connections.py SECRETS_MAX, SECRETS_BYTES_MAX |
Jobs and uploads
| Limit | Value | Source |
|---|---|---|
| A file in the uploaded project | left out over 100 MB | ui/pack.py LARGE_BYTES |
| The uploaded project | refused over 500 MB | ui/pack.py MAX_BYTES |
| An upload's archive | 600 MB | service/api.py WORKSPACE_MAX |
| An upload link | valid 1 hour | service/api.py UPLOAD_EXPIRES_S |
| Uploads no job took | 2 GB and 64 per workspace | service/api.py UPLOAD_BUDGET, UPLOAD_COUNT_MAX |
| A project's name | 1 to 100 characters | ui/jobs.py check_project |
| Jobs running at once | 100 per workspace (queued and running jobs; suspended ones do not count), then 429 for a launch or a resume | service/api.py JOBS_LIVE_MAX |
| Python | 3.12 by default, 3.13 or 3.14 | env/jobenv.py SUPPORTED_PYTHONS |
| Offers tried when there is no capacity | 4 | service/runner.py ALTERNATIVES |
| A pool that had no capacity ranks last | 5 minutes | service/runner.py NO_CAPACITY_S |
| Quotes from one provider | 45 s | service/placement.py OFFERS_TIMEOUT_S |
| A plan or quote from the CLI | 120 s | ui/api.py PLAN_TIMEOUT_S |
| Stop | SIGTERM, then the controller waits 15 s | service/runner.py STOP_GRACE_S |
| A Modal GPU sandbox waiting for capacity | 5 minutes when the job is pinned to one GPU type, 3 minutes when another offer follows, else the start timeout of 15 minutes | providers/modal_.py PINNED_GPU_CREATE_S, FALLBACK_CREATE_S, START_TIMEOUT_S |
| A Modal image build | 30 minutes for the requirements, 10 for Portal's own layer; a failed build is built once more afresh | providers/modal_.py DEPS_BUILD_TIMEOUT_S, PORTAL_BUILD_TIMEOUT_S |
| GPUs in one Modal sandbox | 8 | providers/modal_.py MAX_GPUS |
| Out-of-memory rescues | a capture that fails twice in a row for the same reason ends the job | service/runner.py OOM_CAPTURE_TRIES |
| The memory cap on Linux CPU machines | a reserve of 10% of the machine's memory, 256 MiB to 1 GiB; PORTAL_MEMORY_TRIPWIRE=0 in the job's environment turns it off | runtime/oom.py TRIPWIRE_ENV |
| Why a move waits | said again every 5 minutes per reason | service/runner.py PAUSE_BLOCKED_EVERY_S |
Logs and records
| Limit | Value | Source |
|---|---|---|
| A job's log | the first 256 MiB | service/runner.py LOG_MAX |
| One log read | 256 KiB | service/api.py LOG_LIMIT |
| Events kept per job | the newest 20,000 | service/db.py EVENTS_KEPT |
| Samples kept per job | the newest 20,000, one per 10 s | service/db.py SAMPLES_KEPT, SAMPLE_EVERY_S |
| Samples per answer | 5,000 | service/api.py SAMPLES_MAX |
| Jobs in a listing and in name lookups | the newest 500 | service/api.py JOBS_PAGE |
| The job list and the overview | answers kept 20 s; a job's creation, status change or deletion drops them at once | service/api.py JOBS_TTL_S, OVERVIEW_TTL_S |
Retention
| Item | Kept | Source |
|---|---|---|
| A job's output | 7 days | platform/objects/retention.py OUTPUT_RETENTION_S |
| A job's log | 30 days from when each part was written | storage/setup.py BACKSTOP_DAYS |
| A suspended job's checkpoint | 3 days, a warning 18 hours before | service/settings.py DEFAULTS, storage/retention.py warn_s |
| Checkpoints after an end Portal decided | 7 days | service/runner.py KEEP_ENDED_S |
| A failed move's checkpoint | 1 hour | storage/retention.py FAILED_MOVE_KEEP_S |
| A checkpoint that failed verification | 1 day | storage/retention.py QUARANTINE_KEEP_S |
| An upload no job took | 1 day | platform/objects/retention.py UPLOAD_RETENTION_S |
Machines, leases and notices
| Limit | Value | Source |
|---|---|---|
| A machine's first lease | 90 minutes | service/placement.py FIRST_LEASE_MIN |
| Lease kept ahead | 1 hour (your own machines 24 hours), renewed every 10 minutes | service/policies.py DEFAULTS, service/placement.py LEASE_AHEAD_S |
| Modal and Azure Container Apps | 24 hours at most | service/placement.py FIXED_LIMIT_MIN |
| Google Cloud VMs | 24 hours by default (1 to 2880) | service/settings.py DEFAULTS |
| Ending machines past their lease | checked every 60 s, 2 minutes' grace | service/billing.py REAP_EVERY_S, LEASE_GRACE_S |
| AWS spot notice | 2 minutes | agent/spot.py watch_aws |
| AWS Fargate Spot notice | 120 s | providers/fargate.py STOP_TIMEOUT_S |
| Google Cloud spot notice | 120 s asked for, 25 s if refused | providers/gcp_vm.py instance_body, service/settings.py DEFAULTS |
| Azure spot VM notice | 20 s (the event names a time 22 to 27 s out; the node sees it within about a second) | providers/azure_vm.py SPOT_NOTICE_S, service/notice_fit.py NOTICE_S, agent/spot.py watch_azure |
| Azure Container Apps interruption notice | none: when Azure stops the execution it ends the replica at once (a stopped execution's node was gone 0.55 s later, before any checkpoint, 2026-10-31), and progress since the job's last checkpoint is lost; portal interrupt is refused there | service/notice_fit.py NO_NOTICE, providers/aca.py interrupt |
| Modal GPU sandbox preemption | 5 s | providers/modal_.py SANDBOX_GRACE_S |
| The checkpoint check | at the first pause point, then every 10 minutes; its walk is cut after 5 s; a script that passes no pause point is reported after 30 s | runtime/host.py PROBE_EVERY_S, PROBE_BUDGET_S, PROBE_WAIT_S |
| What a spot notice can save | R x (notice - t0) x 0.5 per provider, vCPU class and route; a measured state above it keeps the job off that spot offer | service/notice_fit.py RATES, NOTICE_S, SAFETY |
| EC2 waiting for capacity or quota | 5 minutes | providers/aws_ec2.py CAPACITY_WAIT_S |
| Lambda launches | one every 12 s; after no capacity, 60 s doubling to 30 minutes | providers/lambda_cloud.py LAUNCH_INTERVAL_S, COOLDOWN_S |
| A machine given notice, silent past its deadline | asked whether it still runs 2 minutes after | service/runner.py NOTICE_LOST_AFTER_S |
| A machine its provider no longer lists | settled as ended, unconfirmed, after 2 hours unlisted | service/billing.py UNLISTED_SETTLE_S |
| What a machine holds against the cap | its lease (Modal sandboxes, Fargate tasks, VMs); a Container Apps replica its 24 hours | service/placement.py LEASE_HELD, FIXED_LIMIT_MIN |
| A Modal create whose reply was lost | settled at $0 once no sandbox exists 60 s after it was asked | providers/modal_.py CREATE_GRACE_S |
| The Azure registry copy, after a failure | tried again after 10 minutes | providers/aca.py MIRROR_RETRY_S |
| A release's stand-down | 10 s at most before the lease is released | platform/api/lease.py STAND_DOWN_S |
Your own machines
| Limit | Value | Source |
|---|---|---|
| An enrollment code | valid 10 minutes, once | service/machines.py ENROLL_TTL_S |
| Approval | within 24 hours | service/machines.py WAITING_MAX_S |
| Wrong pairing codes | the fifth revokes the machine | service/machines.py PAIR_FAILURES_MAX |
| Codes per person | 10 an hour | service/machines.py MINTS_PER_HOUR |
| Waiting at once | 20 codes and unapproved machines | service/machines.py WAITING_MAX |
| Machines per workspace | 50 | service/machines.py MACHINES_MAX |
Policy defaults
| Policy | Default | Source |
|---|---|---|
| Idle | 30 minutes under 5% of a CPU core | service/policies.py DEFAULTS |
Time limit (ttl) | 72 hours of running time; while a job is suspended, the same hours on the clock from its creation end it, its checkpoint kept 7 days for a resume to continue it | service/policies.py DEFAULTS, evaluate; service/runner.py _expire_parked |
| Cheaper and return | every 10 minutes, at least 20% and 3 times the move's cost, 30 minutes after a move | service/policies.py DEFAULTS |
| Budget | acts 2 minutes of the machine's price early | service/policies.py DEFAULTS |
| Reclaimed spot pool | left out 30 minutes | service/policies.py DEFAULTS |
| Spot, state over the notice | moved to on-demand (spot.move_when_over); tried again 10 minutes after a destination could not be had | service/policies.py DEFAULTS, service/runner.py LEAVE_SPOT_RETRY_S |
Every policy and setting, with its default, is on Settings.