Skip to content

feat: warm pool — stop/start idle runners for fast restart - #5204

Draft
Brend-Smits wants to merge 20 commits into
mainfrom
feature/warm-pool-hibernation
Draft

feat: warm pool — stop/start idle runners for fast restart#5204
Brend-Smits wants to merge 20 commits into
mainfrom
feature/warm-pool-hibernation

Conversation

@Brend-Smits

Copy link
Copy Markdown
Contributor

Description

Adds a warm pool feature that reduces runner startup latency from 2–5 minutes to 10–30 seconds by stopping idle instances instead of terminating them.

Three-tier instance lifecycle:

  • Hot (running) — 0s startup, full compute cost
  • Warm (stopped) — 10–30s startup, EBS-only cost
  • Cold (terminated) — 2–5 min startup, zero cost

Key components:

  • DynamoDB state table for warm pool tracking
  • Scale-down: stops instances instead of terminating (with AMI staleness eviction)
  • Scale-up: queries DynamoDB and starts warm instances before cold-launching
  • Pool lambda: pool_strategy = "warm" for zero-idle-compute deployments
  • Full spot instance support via persistent spot requests
  • CloudWatch metrics: WarmPoolStartLatency, WarmPoolEvicted, etc.

See docs/warm-pool.md and ADR-001 for full design.

Test Plan

  • 540 unit tests passing (vitest)
  • 6 Terraform integration tests (warm-pool.tftest.hcl)
  • Manual validation of DynamoDB operations and EC2 stop/start in dev environment

Related Issues

N/A — new feature

Brend-Smits and others added 15 commits July 9, 2026 15:55
- ADR documenting the warm pool concept with three instance states (hot/warm/cold)
- Implementation plan with phased approach
- User-facing warm pool configuration guide

Signed-off-by: Brend Smits <brend.smits@philips.com>
…, IAM

- Add warm_pool_config and pool_strategy variables to root, runners, and multi-runner modules
- Create DynamoDB table with GSI by-owner and TTL for warm pool state
- Add IAM policies for scale-down, scale-up, and pool lambdas (DynamoDB + EC2 stop/start)
- Wire WARM_POOL_CONFIG, WARM_POOL_TABLE_NAME, POOL_STRATEGY env vars to all lambdas
- Add enable_warm_pool metric toggle
- Cross-variable validation: pool_strategy=warm requires warm_pool_config.enabled=true

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Add warm-pool.ts with DynamoDB operations (add, remove, get, list, count)
- Add stopRunner() and startRunner() to runners.ts
- Add emitWarmPoolMetric() for CloudWatch metric publishing
- Export getWarmPoolConfig() and getPoolStrategy() helpers

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Modify removeRunner() to stop instances into warm pool when enabled
- Add DynamoDB record on stop with ghr:warm-pool-member tag
- Respect maxWarmInstances cap (terminate if full)
- Add evictStaleWarmInstances() for periodic cleanup:
  - Evict instances exceeding maxWarmAgeHours
  - Evict instances exceeding maxWarmInstances count
  - Emit WarmPoolSize metric after eviction

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Add findAndStartWarmRunners() to query DynamoDB and start stopped instances
- Integrate warm pool lookup before cold-launching new runners
- Handle org-level fallback when repo-level owner has no warm instances
- Write runner registration config (SSM) for restarted warm instances
- Tag started instances with ghr:started-from-warm-pool, remove ghr:warm-pool-member
- Emit WarmPoolInstanceStarted/WarmPoolStartFailed metrics
- Self-heal stale DynamoDB records on start failure

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Pool counts warm (stopped) instances toward pool target when pool_strategy=warm
- Pool tries to start warm instances before cold-launching new ones
- Remaining deficit is filled by createRunners (cold start)
- Scale-down will stop excess idle runners into warm pool after they run

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Test disabled/hot strategy returns empty
- Test starting and tagging warm instances
- Test failure handling (skip failed, cleanup DynamoDB)
- Test org-level fallback for repo-level owners
- Test multiple instance start up to count
- Test tag failure as non-fatal (best-effort)

Signed-off-by: Brend Smits <brend.smits@philips.com>
Scale-down eviction tests:
- Skip eviction when warm pool disabled
- Evict instances exceeding max age
- Evict instances exceeding max count
- Emit WarmPoolSize metric after eviction

Pool warm strategy tests:
- Count warm instances toward pool target
- Try warm instances before cold launching
- Skip cold launch when warm satisfies full top-up
- Don't count warm instances with hot strategy

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Add amiId field to WarmPoolEntry interface and DynamoDB storage
- Store current AMI ID (from SSM parameter) when stopping instances
- Evict warm instances with stale AMI during eviction cycle
- Add @aws-sdk/client-dynamodb to control-plane dependencies
- Pass AMI_ID_SSM_PARAMETER_NAME env var to scale-down lambda
- Grant scale-down IAM permission to read AMI SSM parameter

Signed-off-by: Brend Smits <brend.smits@philips.com>
Add findAndStartWarmRunners and warm-pool module mocks to
existing pool.test.ts to prevent failures from new imports.

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Verify DynamoDB table not created when disabled (default)
- Verify DynamoDB table created with correct schema when enabled
- Verify IAM policies attached to scale-down, scale-up, and pool roles
- Verify pool_strategy defaults to 'hot'
- Verify warm strategy + enabled config creates resources

Signed-off-by: Brend Smits <brend.smits@philips.com>
- Measure and emit start latency (ms) for warm instance starts
- Emit WarmPoolEvicted metric count during eviction cycles
- Use MetricUnit.Milliseconds for latency metric

Signed-off-by: Brend Smits <brend.smits@philips.com>
When pool_strategy=warm and new instances are cold-launched:
- Wait warmPoolReadyDelaySeconds (default 30s) after creation
- Re-check GitHub runner status for each new instance
- If runner picked up a job: leave running, tag ghr:warm-pool-grace-hit
- If runner is idle: stop instance and add to warm pool

This enables the pool to maintain warm (stopped) instances at zero
idle compute cost while giving jobs a chance to claim new runners.

Signed-off-by: Brend Smits <brend.smits@philips.com>
Signed-off-by: Brend Smits <brend.smits@philips.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

License Issues

lambdas/functions/control-plane/package.json

PackageVersionLicenseIssue Type
@aws-sdk/client-dynamodb^3.1078.0NullUnknown License

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
npm/@aws-sdk/client-dynamodb ^3.1078.0 UnknownUnknown
npm/@aws-sdk/client-dynamodb 3.1078.0 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/core 3.974.26 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-env 3.972.52 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-http 3.972.54 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-ini 3.972.59 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-login 3.972.58 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-node 3.972.61 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-process 3.972.52 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-sso 3.972.58 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/credential-provider-web-identity 3.972.58 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/dynamodb-codec 3.973.26 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/endpoint-cache 3.972.8 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/middleware-endpoint-discovery 3.972.22 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/nested-clients 3.997.26 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/signature-v4-multi-region 3.996.38 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/token-providers 3.1078.0 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/types 3.973.15 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@aws-sdk/xml-builder 3.972.33 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 5detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing🟢 10project is fuzzed
npm/@smithy/core 3.29.0 UnknownUnknown
npm/@smithy/credential-provider-imds 4.4.5 UnknownUnknown
npm/@smithy/fetch-http-handler 5.6.2 UnknownUnknown
npm/@smithy/node-http-handler 4.9.2 UnknownUnknown
npm/@smithy/signature-v4 5.6.1 UnknownUnknown
npm/@smithy/types 4.15.1 UnknownUnknown
npm/mnemonist 0.38.3 🟢 3.2
Details
CheckScoreReason
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Code-Review🟢 3Found 10/30 approved changesets -- score normalized to 3
Maintained⚠️ 12 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/obliterator 1.6.1 UnknownUnknown

Scanned Files

  • lambdas/functions/control-plane/package.json
  • lambdas/yarn.lock

Comment thread lambdas/functions/control-plane/src/pool/pool-warm-strategy.test.ts Fixed
Comment thread lambdas/functions/control-plane/src/pool/pool-warm-strategy.test.ts Fixed
Comment thread lambdas/functions/control-plane/src/pool/pool.test.ts Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “warm pool” lifecycle to the Terraform AWS GitHub Runner module so idle runners can be stopped (EBS-only cost) and later restarted quickly, instead of being terminated and cold-launched.

Changes:

  • Introduces warm_pool_config and pool_strategy inputs and wires them through root, runners, pool, and multi-runner modules.
  • Provisions a DynamoDB warm-pool state table plus IAM policies, and passes warm-pool env vars into scale-up/scale-down/pool lambdas.
  • Implements warm-pool stop/start and eviction logic in the control-plane lambdas, adds unit + Terraform tests, and documents the design (docs + ADRs).

Reviewed changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
variables.tf Adds root-level warm-pool inputs and warm-pool metrics toggle.
README.md Documents new inputs and updates instance allocation strategy wording.
modules/termination-watcher/termination/README.md Regenerates docs to include new IAM policy resource.
modules/termination-watcher/notification/README.md Regenerates docs to include new event rules/targets/permissions/policy resources.
modules/runners/warm-pool.tf Creates warm-pool DynamoDB table + IAM inline policies (new file).
modules/runners/variables.tf Adds warm-pool inputs and metrics toggle in runners module.
modules/runners/tests/warm-pool.tftest.hcl Adds Terraform test coverage for warm-pool resources and validation.
modules/runners/scale-up.tf Passes warm-pool env vars to scale-up lambda.
modules/runners/scale-down.tf Passes warm-pool env vars to scale-down lambda and adds SSM read policy attachment for AMI staleness logic.
modules/runners/README.md Regenerates runners module docs for warm-pool resources/inputs.
modules/runners/pool/variables.tf Extends pool submodule config object to include warm-pool settings.
modules/runners/pool/README.md Regenerates pool submodule docs for extended config schema.
modules/runners/pool/main.tf Passes warm-pool env vars to pool lambda.
modules/runners/pool.tf Wires warm-pool table/config into the pool submodule.
modules/runners/policies/lambda-warm-pool.json New IAM policy template for DynamoDB + EC2 stop/start/tag actions.
modules/multi-runner/variables.tf Adds warm-pool fields to multi-runner config type.
modules/multi-runner/runners.tf Passes warm-pool inputs through to each runners module instance.
modules/multi-runner/README.md Regenerates multi-runner docs to include warm-pool config fields.
mkdocs.yaml Adds warm-pool page to documentation nav.
main.tf Wires root warm-pool inputs into the runners module.
lambdas/yarn.lock Adds @aws-sdk/client-dynamodb dependency lock entries.
lambdas/functions/control-plane/src/scale-runners/scale-up.ts Adds warm instance lookup/start path before cold launching.
lambdas/functions/control-plane/src/scale-runners/scale-down.ts Stops idle instances into warm pool and adds warm eviction logic.
lambdas/functions/control-plane/src/scale-runners/scale-down-warm-pool.test.ts Adds unit tests for eviction behavior.
lambdas/functions/control-plane/src/scale-runners/find-and-start-warm-runners.test.ts Adds unit tests for warm start behavior.
lambdas/functions/control-plane/src/pool/pool.ts Updates pool logic to account for warm instances and implement warm-strategy grace period stop.
lambdas/functions/control-plane/src/pool/pool.test.ts Updates existing pool tests for new wiring/mocks.
lambdas/functions/control-plane/src/pool/pool-warm-strategy.test.ts Adds pool warm-strategy-specific unit tests.
lambdas/functions/control-plane/src/aws/warm-pool.ts New DynamoDB warm-pool state client + metric emitter.
lambdas/functions/control-plane/src/aws/runners.ts Adds EC2 start/stop helpers for warm pool.
lambdas/functions/control-plane/package.json Adds @aws-sdk/client-dynamodb dependency.
docs/warm-pool.md Adds feature documentation and configuration guide.
docs/adr/001-warm-pool-implementation-plan.md Adds detailed phased implementation plan ADR.
docs/adr/001-warm-pool-hibernation.md Adds warm-pool decision ADR and design rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread modules/runners/variables.tf
Comment thread modules/runners/warm-pool.tf Outdated
Comment thread lambdas/functions/control-plane/src/scale-runners/scale-up.ts Outdated
Comment thread lambdas/functions/control-plane/src/scale-runners/scale-down.ts Outdated
Comment thread lambdas/functions/control-plane/src/scale-runners/scale-down.ts Outdated
Comment thread lambdas/functions/control-plane/src/pool/pool.ts
Comment thread lambdas/functions/control-plane/src/pool/pool.ts Outdated
Comment thread lambdas/functions/control-plane/src/aws/warm-pool.ts Outdated
- Remove unused imports in test files (CodeQL warnings)
- Move check block to variable validation (TF >= 1.3 compat)
- Gate scale-up warm starts on warmPoolConfig.enabled only,
  not poolStrategy (scale-up should try warm starts regardless
  of pool lambda strategy)
- Wrap scale-down warm pool stop in try/catch with fallback
  to terminate on failure
- Include stopped instances in eviction owner lookup
- Add AMI ID to pool-created warm instances for staleness eviction
- Make removeFromWarmPool atomic with conditional delete to
  prevent concurrent scale-up race conditions
- Add test for concurrent claim scenario

Signed-off-by: Brend Smits <brend.smits@philips.com>
Cross-variable references in variable validation blocks require
Terraform 1.9+. The repo supports >= 1.3.0, so this breaks CI on
TF 1.5.6. The constraint (pool_strategy=warm requires enabled=true)
is documented in the variable description and enforced at runtime
by the lambda.

Signed-off-by: Brend Smits <brend.smits@philips.com>
@edersonbrilhante

Copy link
Copy Markdown
Contributor

Great idea.

@Brend-Smits

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request. There is a major refactor ongoing on several parts in the control plane. Take this into account and make the needed changes

…ernation

# Conflicts:
#	lambdas/functions/control-plane/src/pool/pool.test.ts
#	lambdas/functions/control-plane/src/pool/pool.ts
#	lambdas/functions/control-plane/src/scale-runners/scale-down.ts
#	lambdas/functions/control-plane/src/scale-runners/scale-up.ts
#	main.tf
#	modules/multi-runner/README.md
#	modules/runners/pool/README.md
#	modules/runners/pool/main.tf
#	modules/runners/scale-down.tf
#	variables.tf
Copilot AI review requested due to automatic review settings August 3, 2026 14:17

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request. There is a major refactor ongoing on several parts in the control plane. Take this into account and make the needed chan...

Merge conflicts resolved in 3cbd134, rebasing the warm pool onto the ongoing control-plane provider refactor.

Rather than patching the old monolithic lambdas, I adopted main's provider-based architecture as the base and re-integrated the warm pool behind the new provider interfaces so it stays EC2-specific:

  • Warm stop/start/eviction now lives in ec2-scale-up.ts, ec2-scale-down.ts, ec2-pool.ts, and shared ec2.ts (extracted registerRunners).
  • Added retire/maintain hooks to the scale-down provider and additionalPoolCapacity to the pool provider; the provider-agnostic scale-up.ts/scale-down.ts/pool.ts are taken from main unchanged apart from these optional hooks.
  • Warm-start is gated on warm_pool_config.enabled; eviction runs via maintain() and gathers owners from running + stopped instances.
  • Terraform conflicts kept both the warm-pool inputs and main's pool_include_busy_runners/RUNNER_PROVIDER_TYPE; module READMEs regenerated with terraform-docs.

All 570 control-plane unit tests pass (warm-pool tests ported to the new provider files), and tsc, eslint, prettier, and terraform fmt are clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 37 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

modules/runners/variables.tf:923

  • pool_strategy = "warm" is described as requiring warm_pool_config.enabled = true, but there’s no validation enforcing this invariant. Without it, users can configure an unsupported combination and the lambdas will receive POOL_STRATEGY=warm without a warm-pool table/policy being created.
    variables.tf:860
  • pool_strategy is documented as requiring warm_pool_config.enabled = true when set to warm, but this is not enforced. This allows invalid configurations to plan/apply and then fail at runtime when warm-pool resources/env vars are missing.
    lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.ts:140
  • startWarmInstances currently iterates DynamoDB query results in the order returned by listWarmInstancesByOwner, which is configured as ScanIndexForward: true (oldest first). This causes scale-up to start the stalest warm instances first, conflicting with the docs/ADR intent of preferring the freshest stopped instance.

  const startedInstances: string[] = [];

lambdas/functions/control-plane/src/pool/ec2-pool.ts:128

  • When a runner becomes busy during the warm-strategy grace window, the code emits WarmPoolInstanceStarted. That metric name implies a warm-pool restart, but this path is a normal (cold) start that simply avoided being stopped. This will inflate warm-start counts and make metrics misleading.
    if (status?.busy) {
      // Runner picked up a job during the grace window — leave it running.
      logger.info(`Runner '${instanceId}' picked up a job during grace period, leaving running`);
      await tag(instanceId, [{ Key: 'ghr:warm-pool-grace-hit', Value: 'true' }]).catch(() => {
        /* best-effort */
      });
      emitWarmPoolMetric('WarmPoolInstanceStarted', 1, { Owner: runnerOwner });
    } else {

"Resource": "*",
"Condition": {
"StringEquals": {
"ec2:ResourceTag/gh:environment": "${environment}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants