RunAcross
Concurrent Python execution across AWS accounts.
RunAcross handles STS AssumeRole, concurrent execution, error isolation and result aggregation so your code can focus on the AWS operation itself.
RunAcross 0.1 executes across accounts. Account-by-Region execution is planned for a later release.
Why RunAcross?
Multi-account scripts repeatedly need the same plumbing:
accounts
-> STS AssumeRole
-> boto3 Session
-> ThreadPoolExecutor
-> callback
-> isolated errors
-> aggregated results
RunAcross packages that pattern as a small synchronous function. It is a library primitive, not a scanner, CLI, scheduler, or credentials manager.
Installation
pip install runacross
RunAcross requires Python 3.10 or later. For development from a local clone:
python -m pip install -e ".[dev]"
Quickstart
from runacross import map_accounts
def who_am_i(session, account):
sts = session.client("sts")
return sts.get_caller_identity()["Arn"]
results = map_accounts(
who_am_i,
accounts=[
"111111111111",
"222222222222",
],
role_name="SecurityAuditRole",
max_workers=10,
)
for result in results:
if result.success:
print(f"{result.account.id}: {result.value}")
else:
print(f"{result.account.id}: {result.phase}: {result.error}")
Account IDs are converted to immutable Account objects. You can also provide
metadata explicitly:
from runacross import Account
accounts = [
Account(id="111111111111", name="Production"),
Account(id="222222222222", name="Development"),
]
Using AWS Organizations
Organizations is an optional, explicit source of accounts:
from runacross import map_accounts
from runacross.organizations import list_accounts
accounts = list_accounts(
organization_id="o-exampleorgid",
exclude_accounts=["111111111111"],
)
results = map_accounts(
who_am_i,
accounts=accounts,
role_name="SecurityAuditRole",
)
The organization ID is a safety check, not a selector. AWS uses the source
credentials to determine which organization is visible. RunAcross verifies
that it matches the expected ID and then returns accounts whose current
Organizations State is ACTIVE.
Call list_accounts() without an ID when that guard is not needed.
Discovered Account objects include the Organizations name and root email
address; treat those fields as sensitive.
Handling failures
One failed account does not cancel the others:
for result in results:
if result.success:
use(result.value)
elif result.phase == "assume_role":
report_access_problem(result.account, result.error)
else:
report_worker_problem(result.account, result.error)
RunResults preserves input order and provides:
results.successful
results.failed
results.success_count
results.failure_count
Use result.unwrap() when code wants the typed value or the stored exception:
for result in results:
try:
value = result.unwrap()
except Exception as error:
handle(result.account, error)
RunAcross does not retry the callback because arbitrary functions may not be idempotent.
Concurrency
RunAcross uses ThreadPoolExecutor, which is suitable for the network-bound
work performed by Boto3. The default is 10 workers:
results = map_accounts(
who_am_i,
accounts=accounts,
role_name="SecurityAuditRole",
max_workers=5,
)
More workers do not imply linear speedups. High concurrency can increase throttling, connection use, memory use, and Lambda duration.
RunAcross-owned clients use Botocore standard retries with three total attempts. Override them explicitly when needed:
from botocore.config import Config
results = map_accounts(
who_am_i,
accounts=accounts,
role_name="SecurityAuditRole",
botocore_config=Config(
retries={"mode": "adaptive", "total_max_attempts": 5},
),
)
This configuration applies only to clients RunAcross creates, such as STS.
Pass a Config to clients created inside your callback to configure their
retries.
Authentication
By default, RunAcross uses boto3.Session() and the standard Boto3 credential
provider chain. It works with configured environment credentials, profiles,
IAM Identity Center, web identity, ECS task roles, EC2 instance profiles,
Lambda execution roles, and GitHub Actions OIDC.
You can provide a source Session:
import boto3
source_session = boto3.Session(profile_name="security")
results = map_accounts(
who_am_i,
accounts=accounts,
role_name="SecurityAuditRole",
source_session=source_session,
)
The target role must trust the source identity, and the source identity must
be allowed to call sts:AssumeRole.
Assumed Sessions in 0.1 do not refresh automatically. Callbacks should finish within the STS session lifetime, normally one hour.
IAM permissions
The source identity needs sts:AssumeRole for the target roles. Organizations
discovery additionally needs organizations:ListAccounts; using the
organization ID guard also needs organizations:DescribeOrganization.
The assumed role needs only the service permissions used by the callback. See docs/iam.md for restrictive examples and trust-policy requirements.
Running in AWS Lambda
Lambda execution-role credentials are discovered automatically. Package RunAcross and its Boto3 dependency with the function or in a layer so the versions are controlled by your deployment.
All callbacks must finish before the handler returns. Do not leave RunAcross
work running in the background between Lambda invocations. Tune max_workers
for the function's memory, timeout, and downstream AWS quotas.
Security
RunAcross does not persist or return STS credentials, add telemetry, or create non-AWS service clients. Library logging is silent unless the application configures it. RunAcross never deliberately adds credentials to logs, but callback exception messages are emitted at DEBUG and must not contain secrets.
See SECURITY.md for vulnerability reporting.
Roadmap
Planned areas include explicit account-by-Region execution, enabled-Region discovery, and small Organizations filters. RunAcross will remain a library primitive rather than becoming an orchestration framework.
See docs/roadmap.md.
Contributing
Development uses pytest, Ruff, and mypy. See CONTRIBUTING.md.
RunAcross is licensed under the Apache License 2.0.
Metadata
Release files for runacross 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| runacross-0.1.0.tar.gz | 27.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| runacross-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.1 kB
Release files / runacross-0.1.0.tar.gz
| Download URL | runacross-0.1.0.tar.gz |
|---|---|
| Size | 27.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f95844fd1d37011846f7cd6bd42e7da81ee5a4b1d6bd3e7ef04491886dd2777
|
|
BLAKE2b-256 checksum How to use checksums |
224079faa159d39b273f49635431a0fe7d9dacedec19ceba8b1430c4739ade75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency logRelease files / runacross-0.1.0-py3-none-any.whl
| Download URL | runacross-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
784d31945e9022dde8634a101c7bd0ae3f46b591467a1b877d33e456340ec321
|
|
BLAKE2b-256 checksum How to use checksums |
15e4fcd5f629e1c8feb0e6a096b016a4a7d85d823194ce0bc4a51a26c2f26ce9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency log