Skip to content

fix(ssm): detect ParameterNotFound when the SDK error name is generic - #5563

Draft
oxatruchdev wants to merge 1 commit into
github-aws-runners:mainfrom
oxatruchdev:fix/runner-group-cache-miss
Draft

oxatruchdev wants to merge 1 commit into
github-aws-runners:mainfrom
oxatruchdev:fix/runner-group-cache-miss

Conversation

@oxatruchdev

Copy link
Copy Markdown

Description

What fails

On a multi-runner deployment of v8.0.0 with ephemeral runners using JIT config, every launch fails on a runner group cache miss. The scale-up Lambda logs Runner group cache lookup failed followed by Unexpected error while registering GitHub runners. The instances are terminated and the SQS message is retried indefinitely.

The error that reaches AwsSsmRunnerGroupCacheStore.get() is:

GetParameterError: ParameterNotFound            (name: 'GetParameterError')
    at SSMProvider_SSMProvider.get ...
    at src_getParameter ...
    at AwsSsmRunnerGroupCacheStore.get ...
    at getRunnerGroupId ...
    at createJitConfig ...
  cause: Error: ParameterNotFound               (name: 'Error')
    at ProtocolLib.getErrorSchemaOrThrowBaseException ...
    at AwsJson1_1Protocol.handleError ...
    at AwsJson1_1Protocol.deserializeResponse ...

errorNames: ['GetParameterError', 'Error']

Root cause

isParameterNotFoundError in runner-group-cache-store.ts only matched errors in the cause chain whose name is ParameterNotFound. In the error above, neither error in the chain has that name.

In the @aws-sdk/core version locked at v8.0.0 (3.973.23), ProtocolLib.getErrorSchemaOrThrowBaseException has a fallback for when the modeled exception schema cannot be resolved from the type registry and no synthetic base exception is registered:

dataObject.message = dataObject.message ?? dataObject.Message ?? "UnknownError";
...
throw this.decorateServiceException(Object.assign(new Error(errorName), errorMetadata), dataObject);

So the thrown error has:

  • name: 'Error' (plain Error)
  • message: 'ParameterNotFound' (the error code)
  • $fault: 'client' and $metadata (HTTP status, request id, ...)
  • every field of the parsed JSON error body, copied by decorateServiceException. For awsJson1_1 this includes __type (ParameterNotFound, possibly namespaced ...#ParameterNotFound). The body's message is not copied because the error already has one.

The current @aws-sdk/core (3.978.x) builds this fallback as new Error(message) with name: errorName, so the name survives there. The store should still not depend on which path the SDK takes. runner-config-consumer.ts had the same name-only check (isSsmNotFound) on its raw GetParameterCommand path. With the fallback error it would stop polling with Failed to read runner configuration instead of waiting for the parameter.

Fix

This adds a shared helper, aws/ssm/parameter-not-found.ts (isParameterNotFoundError), which walks the cause chain (guarded against cycles) and treats an error as "parameter not found" if any error in the chain:

  • has name === 'ParameterNotFound' (current behaviour), or
  • has an SDK error code field (__type, Code or code) equal to ParameterNotFound after the SDK's own normalisation (ns#Code:extra → Code), or
  • has a generic name (Error or none) and message === 'ParameterNotFound'.

The check stays narrow. Access denied, throttling, ParameterVersionNotFound and other errors are still rethrown, and a specifically named error (e.g. GetParameterError) is not matched on its message alone. Both the runner group cache store and the runner config consumer now use the helper.

Workaround for affected deployments

Create the cache parameter by hand with the runner group ID: /github-action-runners/<prefix>/<runner>/runners/config/runner-group/<group name>.

Test Plan

  • New parameter-not-found.test.ts: matches by name (direct and wrapped), by message on a generic Error, and by __type / namespaced __type / Code / code. It does not match AccessDeniedException, ThrottlingException, ParameterVersionNotFound, InvalidKeyId, a GetParameterError that only has the message, non-objects, or cyclic chains.
  • runner-group-cache-store.test.ts: reproduces the production shape exactly (GetParameterError wrapping an Error named Error with message ParameterNotFound), plus a variant carrying __type. Both assert that get() returns undefined and logs a cache miss. A negative test checks that a generic Error with a different code is still rethrown. The existing returns undefined only for ParameterNotFound and propagates access and service errors tests are unchanged and pass.
  • runner-config-consumer.test.ts: keeps polling when the SDK reports a missing parameter as a generic Error.
  • The 3 new store/consumer tests fail against the previous implementation and pass with this change.
  • cd lambdas && yarn install && yarn format-check && yarn lint && yarn test: lint passes for all 11 projects, and all 1422 tests pass (storage-providers: 161).

Related Issues

Regression from the runner group cache store extraction in #5355.

The runner group cache store and the runner config consumer only treated an
SSM error as "parameter not found" when its `name` was `ParameterNotFound`.
When the AWS SDK cannot resolve the modeled exception class it throws a plain
`Error` whose name is `Error` and whose message is the error code, decorated
with `$fault`, `$metadata` and the parsed error body (`__type`). The cache
miss was then rethrown instead of returning `undefined`, so every ephemeral
JIT launch failed until the cache parameter was created by hand.

Add a shared `isParameterNotFoundError` helper that walks the cause chain and
matches the modeled name, the SDK error code fields (`__type`, `Code`, `code`)
or the `ParameterNotFound` message on a generically named error, and use it in
both stores. Other errors (access denied, throttling, ...) are still rethrown.
@oxatruchdev
oxatruchdev requested a review from a team as a code owner October 30, 2026 00:49
@oxatruchdev
oxatruchdev marked this pull request as draft October 30, 2026 00:58

This branch has not been deployed

No deployments
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.

1 participant