Skip to main content

RunAcross

CI PyPI

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)

Source distribution for runacross 0.1.0
File Size Uploaded
runacross-0.1.0.tar.gz 27.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for runacross 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page