Skip to content

feat: propagate host-language stack traces through the jsii protocol#5153

Open
otaviomacedo wants to merge 4 commits into
mainfrom
otaviom/stack-traces
Open

feat: propagate host-language stack traces through the jsii protocol#5153
otaviomacedo wants to merge 4 commits into
mainfrom
otaviom/stack-traces

Conversation

@otaviomacedo

@otaviomacedo otaviomacedo commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The problem

When using the AWS CDK from a jsii-hosted language (Python, Java, Go, .NET), the CDK captures stack traces to record in construct metadata — for example, to show where a construct was instantiated or where a property was assigned. However, these stack traces are captured inside the Node.js kernel process, so they contain JavaScript frames that are meaningless to the user. A Python developer sees references to /tmp/jsii-kernel-xxxx/node_modules/aws-cdk-lib/... instead of their own my_stack.py:42.

Overview of the solution

We extend the jsii wire protocol with an optional $jsii.stacktrace field that host runtimes can attach to any request. The kernel extracts this field and makes it available to JavaScript code (such as the CDK) via a well-known global Symbol — no dependency between the CDK and jsii is required. The feature is opt-in, controlled by the JSII_HOST_STACK_TRACES=1 environment variable.

This PR implements the full pipeline for Python and the kernel-side plumbing. Java, Go, and .NET runtimes will follow in separate PRs, and CDK integration (reading the global instead of capturing a JS stack) is a separate change against the aws-cdk repository.

Data flow

Python user code
        │
        ▼
jsii Python runtime (_kernel/providers/process.py)
        │ captures stack trace via traceback.extract_stack()
        │ filters out jsii-internal and synthetic frames
        │ attaches result as "$jsii.stacktrace" on the JSON request
        ▼
┌─────────── stdio ────────────┐
│ JSON request with            │
│  "$jsii.stacktrace" field    │
└──────────────────────────────┘
        │
        ▼
@jsii/runtime KernelHost (host.ts)
        │ extracts "$jsii.stacktrace" from the request
        │ sets global[Symbol.for('jsii.context.hostStackTrace')]
        │ dispatches the kernel method (e.g., create, set, invoke)
        │
        ▼
JavaScript library code (e.g., CDK constructs)
        │ reads global[Symbol.for('jsii.context.hostStackTrace')]
        │ uses it in place of new Error().stack
        │
        ▼
@jsii/runtime KernelHost (host.ts)
        │ clears the global in finally block
        │ writes the response
        ▼
┌─────────── stdio ────────────┐
│ JSON response                │
└──────────────────────────────┘

Wire format

The $jsii.stacktrace field is an array of frames, each represented as a 4-element tuple:

{
  "api": "create",
  "fqn": "aws-cdk-lib.aws_s3.Bucket",
  "args": [
    ...
  ],
  "$jsii.stacktrace": [
    [
      "my_app/my_stack.py",
      42,
      0,
      "MyStack.__init__"
    ],
    [
      "app.py",
      12,
      0,
      "<module>"
    ]
  ]
}
┌───────┬──────────┬────────┬─────────────────────────────────────────────────┐
│ Index │  Field   │  Type  │                   Description                   │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 0     │ file     │ string │ Source file path                                │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 1     │ line     │ number │ 1-indexed line number                           │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 2     │ column   │ number │ 0-indexed column number (0 when unavailable)    │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 3     │ function │ string │ Qualified function name (e.g. MyStack.__init__) │
└───────┴──────────┴────────┴─────────────────────────────────────────────────┘

Frames are ordered most-recent-first, matching the convention of V8's Error.stack. The field is omitted entirely when the feature is disabled, adding zero overhead to the default path.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

## The problem

When using the AWS CDK from a jsii-hosted language (Python, Java, Go, .NET), the CDK captures stack traces to record in
construct metadata — for example, to show where a construct was instantiated or where a property was assigned.
However, these stack traces are captured inside the Node.js kernel process, so they contain JavaScript frames that are
meaningless to the user. A Python developer sees references to
`/tmp/jsii-kernel-xxxx/node_modules/aws-cdk-lib/...` instead of their own `my_stack.py:42`.

## Overview of the solution

We extend the jsii wire protocol with an optional `$jsii.stacktrace` field that host runtimes can attach to any request.
The kernel extracts this field and makes it available to JavaScript code (such as the CDK) via a well-known
global Symbol — no dependency between the CDK and jsii is required. The feature is opt-in, controlled by the
`JSII_HOST_STACK_TRACES=1` environment variable.

This PR implements the full pipeline for Python and the kernel-side plumbing. Java, Go, and .NET runtimes will follow in
separate PRs, and CDK integration (reading the global instead of capturing a JS stack) is a separate change
against the aws-cdk repository.

## Data flow

    Python user code
            │
            ▼
    jsii Python runtime (_kernel/providers/process.py)
            │ captures stack trace via traceback.extract_stack()
            │ filters out jsii-internal and synthetic frames
            │ attaches result as "$jsii.stacktrace" on the JSON request
            ▼
    ┌─────────── stdio ────────────┐
    │ JSON request with            │
    │  "$jsii.stacktrace" field    │
    └──────────────────────────────┘
            │
            ▼
    @jsii/runtime KernelHost (host.ts)
            │ extracts "$jsii.stacktrace" from the request
            │ sets global[Symbol.for('jsii.context.hostStackTrace')]
            │ dispatches the kernel method (e.g., create, set, invoke)
            │
            ▼
    JavaScript library code (e.g., CDK constructs)
            │ reads global[Symbol.for('jsii.context.hostStackTrace')]
            │ uses it in place of new Error().stack
            │
            ▼
    @jsii/runtime KernelHost (host.ts)
            │ clears the global in finally block
            │ writes the response
            ▼
    ┌─────────── stdio ────────────┐
    │ JSON response                │
    └──────────────────────────────┘

## Data format

The `$jsii.stacktrace` field is an array of frames, each represented as a 4-element tuple:

```json
{
  "api": "create",
  "fqn": "aws-cdk-lib.aws_s3.Bucket",
  "args": [
    ...
  ],
  "$jsii.stacktrace": [
    [
      "my_app/my_stack.py",
      42,
      0,
      "MyStack.__init__"
    ],
    [
      "app.py",
      12,
      0,
      "<module>"
    ]
  ]
}
```

```
┌───────┬──────────┬────────┬─────────────────────────────────────────────────┐
│ Index │  Field   │  Type  │                   Description                   │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 0     │ file     │ string │ Source file path                                │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 1     │ line     │ number │ 1-indexed line number                           │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 2     │ column   │ number │ 0-indexed column number (0 when unavailable)    │
├───────┼──────────┼────────┼─────────────────────────────────────────────────┤
│ 3     │ function │ string │ Qualified function name (e.g. MyStack.__init__) │
└───────┴──────────┴────────┴─────────────────────────────────────────────────┘
```

Frames are ordered most-recent-first, matching the convention of V8's Error.stack. The field is omitted entirely when
the feature is disabled, adding zero overhead to the default path.
@mergify mergify Bot added the contribution/core This is a PR that came from AWS. label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution/core This is a PR that came from AWS.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant