Skip to content

Fix bytes envelope round-trip across JS-Python boundary#194

Merged
bbopen merged 3 commits intomainfrom
codex/tywrap-open-issue-reliability-pass
Feb 16, 2026
Merged

Fix bytes envelope round-trip across JS-Python boundary#194
bbopen merged 3 commits intomainfrom
codex/tywrap-open-issue-reliability-pass

Conversation

@bbopen
Copy link
Owner

@bbopen bbopen commented Feb 16, 2026

Summary

Fixes byte-boundary interoperability between JavaScript and Python for Tywrap runtime calls.

  • Python bridge now deserializes byte envelopes from JS into real bytes in:
    • handle_call
    • handle_instantiate
    • handle_call_method
  • SafeCodec now revives Python/JS bytes envelopes in both:
    • decodeResponse
    • decodeResponseAsync
  • Added guard for timer unref() in ProcessIO to avoid runtime crashes on runtimes without Node timer unref semantics.
  • Added regression tests covering:
    • Uint8Array round-trip through Python len/bytes
    • Python bytes envelope decoding in SafeCodec

Validation

  • npx vitest --run test/safe-codec.test.ts test/runtime_node.test.ts

Closes #193.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Caution

Review failed

The pull request is closed.

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b5137b and aca2f5d.

📒 Files selected for processing (4)
  • runtime/python_bridge.py
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts

📝 Walkthrough

Walkthrough

Added bidirectional bytes handling at the JS↔Python bridge: JS byte envelopes are deserialized into Python bytes at the Python bridge boundary, and Python byte envelopes are revived into Uint8Array during TypeScript JSON parsing; tests and a small runtime compatibility guard were added.

Changes

Cohort / File(s) Summary
Python Bridge
runtime/python_bridge.py
Added _NO_DESERIALIZE sentinel, _deserialize_bytes_envelope() helper, and public deserialize(value) to convert base64 byte envelopes into Python bytes. Wired deserialize(...) into handle_call, handle_instantiate, and handle_call_method for args/kwargs.
TypeScript SafeCodec
src/runtime/safe-codec.ts
Added fromBase64() and assertValidBase64() helpers, a reviveValue JSON reviver and bound property; switched JSON.parse in decodeResponse/decodeResponseAsync to use the reviver so Python bytes envelopes become Uint8Array. Enhanced parse error wrapping.
Process I/O guard
src/runtime/process-io.ts
Guarded timer.unref() call with a runtime check to avoid calling unref() in non-Node environments.
Tests
test/runtime_node.test.ts, test/safe-codec.test.ts
Added tests for Uint8Array ↔ Python bytes roundtrip, zero-length byte handling, and error paths for malformed/invalid base64 bytes envelopes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as JS Client
    participant Codec as SafeCodec
    participant IO as Process I/O
    participant Bridge as Python Bridge
    participant Python as Python Runtime

    rect rgba(100,150,200,0.5)
    Note over Client,Python: Request flow (JS bytes -> Python bytes)
    Client->>Codec: prepare args (Uint8Array)
    Codec->>Codec: encode as __tywrap_bytes__ envelope
    Codec->>IO: send JSON payload
    IO->>Bridge: deliver request
    Bridge->>Bridge: deserialize(...) -> detect envelope
    Bridge->>Bridge: _deserialize_bytes_envelope() -> Python bytes
    Bridge->>Python: invoke function with native bytes
    end

    rect rgba(200,150,100,0.5)
    Note over Python,Client: Response flow (Python bytes -> JS Uint8Array)
    Python->>Bridge: return result with bytes envelope (__type__: 'bytes' or __tywrap_bytes__)
    Bridge->>IO: send JSON response
    IO->>Codec: pass payload
    Codec->>Codec: JSON.parse(..., reviveValue) -> fromBase64 -> Uint8Array
    Codec->>Client: deliver typed result
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I chewed base64 into tiny bites,
Unwrapped envelopes beneath moonlights,
Bytes hop back and forth in tune,
From JS burrow to Python dune,
Hooray — the bridge hums through the nights!

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main change: fixing bytes envelope round-trip across the JS-Python boundary, which is the core objective of the pull request.
Description check ✅ Passed The description clearly details the changes across all affected files and their purpose, relating directly to the changeset and objectives.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from issue #193: Python bytes deserialization in handle_call/handle_instantiate/handle_call_method [#193], SafeCodec reviver for bytes envelopes [#193], regression tests [#193], and constrained scope [#193].
Out of Scope Changes check ✅ Passed All changes are directly related to bytes envelope handling at the JS-Python bridge boundary. The timer unref() guard in ProcessIO is a minimal compatibility fix supporting the bridge's runtime requirements.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/tywrap-open-issue-reliability-pass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added area:codec Area: codecs and serialization area:runtime-node Area: Node runtime bridge bug Something isn't working priority:p2 Priority P2 (medium) labels Feb 16, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/cli.ts (1)

316-316: ⚠️ Potential issue | 🔴 Critical

Bug: res.warnings.length will throw if res.warnings is undefined.

Line 290 defensively assigns const warnings = res.warnings ?? [], but line 316 (and line 358) accesses res.warnings.length directly, bypassing the guard. If the generate() result omits warnings, this is a runtime TypeError.

Use the local warnings variable consistently.

🐛 Proposed fix
-              if (!(argv.failOnWarn && res.warnings.length > 0)) {
+              if (!(argv.failOnWarn && warnings.length > 0)) {
                 process.exit(3);
               }

And at line 358:

-          if (argv.failOnWarn && res.warnings.length > 0) {
+          if (argv.failOnWarn && warnings.length > 0) {
             log.error(
-              `Warnings encountered (count ${res.warnings.length}). Failing due to --fail-on-warn.`
+              `Warnings encountered (count ${warnings.length}). Failing due to --fail-on-warn.`
             );
src/core/generator.ts (1)

157-384: 🛠️ Refactor suggestion | 🟠 Major

Significant DRY violation: kwargs/varargs generation logic is triplicated.

The same ~100-line pattern (compute params → build kwargs type → render overloads → build call prelude → build guards) is copy-pasted across:

  1. generateFunctionWrapper (lines 157–384)
  2. Class method bodies in generateClassWrapper (lines 478–651)
  3. Constructor spec in generateClassWrapper (lines 657–833)

Each copy differs only in indentation depth and error-message identifiers. A bug fix or feature addition to one copy must be manually replicated to the other two. Consider extracting the shared logic into a private helper that accepts the parameter list and context (function name, indent level) and returns the generated fragments (overloads, prelude, guards, param declaration).

Also applies to: 478-651, 657-833

docs/getting-started.md (1)

143-176: 🧹 Nitpick | 🔵 Trivial

Helpful addition for non-importable module guidance.

The pythonImportPath documentation and the fallback example are clear and actionable.

One minor note: consider adding a brief comment or note that "pythonImportPath": [] is the default and can be omitted, to avoid confusion for new users who might think it's required.

src/runtime/safe-codec.ts (1)

601-613: ⚠️ Potential issue | 🟠 Major

String.fromCharCode(...bytes) will throw RangeError for large Uint8Array inputs in the browser path.

The spread operator passes each byte as a separate argument to fromCharCode, which is limited by the JS engine's maximum call stack / argument count (typically ~65 536 on V8). For byte arrays larger than ~64 KB, this will crash in the atob/browser fallback path. The fromBase64 method (line 343) avoids this issue by using Array.from.

Consider chunking the conversion:

🔧 Proposed fix
   private toBase64(bytes: Uint8Array): string {
     if (typeof Buffer !== 'undefined') {
       return Buffer.from(bytes).toString('base64');
     }
     if (globalThis.btoa) {
-      const binary = String.fromCharCode(...bytes);
+      let binary = '';
+      for (let i = 0; i < bytes.length; i++) {
+        binary += String.fromCharCode(bytes[i]);
+      }
       return globalThis.btoa(binary);
     }
🤖 Fix all issues with AI agents
In `@runtime/python_bridge.py`:
- Around line 161-168: The guard rejecting empty base64 strings prevents
zero-length bytes from decoding; in runtime/python_bridge.py update the checks
that validate the "b64" field (the block that runs when
value.get('__tywrap_bytes__') is True and the parallel legacy envelope block) to
only assert the type (e.g., isinstance(b64, str)) and not reject empty strings,
and keep the existing base64.b64decode try/except to raise ProtocolError on
invalid base64; make the same change in the legacy envelope branch so valid b""
round-trips successfully.

In `@src/core/annotation-parser.ts`:
- Around line 238-260: The Callable parsing block uses tab indentation causing a
style inconsistency; locate the block that begins with the
raw.startsWith('typing.Callable[') || raw.startsWith('Callable[') check (inside
the parse function where splitTopLevel and parse are used) and replace the
tab-based indentation with the file's standard space-based indentation to match
surrounding lines and satisfy linting/style rules.

In `@src/core/generator.ts`:
- Around line 533-537: The variable requiredPosCount declared inside the
overload-generation blocks shadows an outer variable of the same name; update
the inner declaration(s) (the one inside the block that uses needsKwargsParam,
requiredKwOnlyNames and positionalParams, and the analogous declaration in the
constructor spec overload-generation) to a distinct name such as
overloadRequiredPosCount to avoid shadowing and lint warnings, and update all
references within those blocks to use the new name.
- Around line 647-651: The generated method body contains a closing brace line
that uses tabs instead of spaces (e.g., the template string that returns
`${overloadDecl}  async ${mname}(${paramsDecl}): Promise<${returnType}> { ...
}`), causing mixed tabs/spaces at the end (`		  }`); update the template in
generator.ts to replace the tab-indented closing line with spaces so its
indentation matches the surrounding lines (adjust the same pattern where the
template emits the closing brace around callPrelude/guards and
getRuntimeBridge().callMethod(...), and also fix the similar occurrences around
lines that generate other method bodies noted in the diff).

In `@src/runtime/safe-codec.ts`:
- Around line 508-517: The code currently calls JSON.parse(payload,
this.reviveValue.bind(this)) inside the decode path which allocates a new bound
function on every call; bind reviveValue once in the class constructor and store
it to a private field (e.g., this._reviveValue) and then use that field in the
JSON.parse call (replace this.reviveValue.bind(this) with this._reviveValue) so
reviveValue remains correctly bound without repeated allocations; update the
constructor to set this._reviveValue = this.reviveValue.bind(this) and change
the JSON.parse usage in the decode/JSON parse block accordingly.

In `@src/types/global.d.ts`:
- Around line 47-55: Update the kill() signature in the Subprocess return type
to match Bun's actual API: change the parameter name from signal? to exitCode?
and its type from number | string to number | NodeJS.Signals so the method reads
like kill(exitCode?: number | NodeJS.Signals): void; update any related
declarations that reference kill() in this type block to ensure tooling and
autocompletion use NodeJS.Signals.

In `@src/tywrap.ts`:
- Around line 215-229: The current filter in moduleModel.functions applies
exclude precedence over an explicit allow-list (moduleConfig.functions) by
checking allow.has(f.name) && !shouldExclude(f.name, false); decide the intended
precedence and update accordingly: if allow-list should win, remove the exclude
check so the filter becomes solely based on allow.has(f.name); if exclude should
win, keep the logic but add documentation explaining this precedence in
docs/configuration.md; ensure references to moduleConfig.functions,
moduleModel.functions, allow, and shouldExclude reflect the chosen behavior and
adjust any related warnings/messages if needed.
- Around line 190-200: The RegExp construction for user-supplied patterns
(excludeRegexes created from moduleConfig.excludePatterns in tywrap.ts) can be
vulnerable to ReDoS; update the logic to guard against catastrophic patterns by
validating or limiting patterns before calling new RegExp: e.g., enforce a max
pattern length, reject patterns with known exponential constructs (like nested
quantifiers) or run them through a safe-regex / regex-sanitizer helper, and if a
pattern is rejected push a warning into warnings with moduleKey explaining
rejection; keep the existing try/catch as a fallback and ensure any helper used
is referenced (e.g., safeRegexCheck or isComplexRegex) so reviewers can locate
the new validation step.

In `@src/utils/ir-cache.ts`:
- Around line 44-49: computeIrCacheFilename is currently using digest.slice(0,
32) which collapses to all zeros when hashUtils.sha256Hex falls back to the
32-bit DJB2 implementation (padded to 64 hex chars); update
computeIrCacheFilename to use the meaningful portion of the digest (for example
digest.slice(-32) to take the last 32 hex chars) so filenames differ under the
DJB2 fallback, ensuring the change is applied where computeIrCacheFilename
constructs the filename and that it still produces a stable ASCII filename like
`ir_<hex>.json`.

In `@src/utils/runtime.ts`:
- Around line 718-727: Replace tab characters in the DJB2 fallback block inside
the sha256Hex function with the project's space-based indentation to match the
rest of the file; specifically update the lines that initialize hash, the
for-loop, the hash calculations, and the return so they use spaces, and verify
the closing braces for sha256Hex and hashUtils align with surrounding code so
the function and object close visually and syntactically consistent with other
methods.

In `@test/parallel-processor.test.ts`:
- Around line 696-720: The test's fixed 50ms sleep is flaky; replace it with a
small polling loop that waits until processor.getStats().activeTasks > 0 (or a
short timeout like 1s) before calling processor.dispose(), so you ensure the
task is actually in-flight; update the it block around the executeTasks/await
processor.dispose() sequence to poll getStats().activeTasks (or fall back to a
longer delay with a comment) to avoid CI timing issues while preserving the
existing test timeout.

In `@test/process_utils_timeout.test.ts`:
- Around line 7-19: The availability probe uses spawnSync(pythonPath,
['--version'], { encoding: 'utf-8' }) without a timeout, so if the Python binary
hangs the test blocks; update that probe call in
test/process_utils_timeout.test.ts (the spawnSync check near
getDefaultPythonPath() and timeoutMs) to include a short timeout option (e.g.,
~1000ms) in its options so the guard self-contains its failure path and returns
early instead of relying on the outer Vitest timeout.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 620a19e and f479efc.

📒 Files selected for processing (55)
  • docs/configuration.md
  • docs/getting-started.md
  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • runtime/python_bridge.py
  • src/cli.ts
  • src/config/index.ts
  • src/core/analyzer.ts
  • src/core/annotation-parser.ts
  • src/core/discovery.ts
  • src/core/generator.ts
  • src/index.ts
  • src/runtime/errors.ts
  • src/runtime/http-io.ts
  • src/runtime/http.ts
  • src/runtime/node.ts
  • src/runtime/optimized-node.ts
  • src/runtime/process-io.ts
  • src/runtime/pyodide-io.ts
  • src/runtime/safe-codec.ts
  • src/runtime/worker-pool.ts
  • src/types/global.d.ts
  • src/types/index.ts
  • src/tywrap.ts
  • src/utils/cache.ts
  • src/utils/codec.ts
  • src/utils/ir-cache.ts
  • src/utils/parallel-processor.ts
  • src/utils/runtime.ts
  • test/adversarial_playground.test.ts
  • test/analyzer.test.ts
  • test/annotation-parser.test.ts
  • test/arch-stories.test.ts
  • test/bounded-context.test.ts
  • test/bridge-protocol.test.ts
  • test/cache_generateKey.test.ts
  • test/cli.test.ts
  • test/code_generation_quality.test.ts
  • test/config_loading.test.ts
  • test/discovery.test.ts
  • test/disposable.test.ts
  • test/generator.test.ts
  • test/integration.test.ts
  • test/ir_cache_filename.test.ts
  • test/optimized-node.test.ts
  • test/parallel-processor.test.ts
  • test/process_utils_timeout.test.ts
  • test/runtime_codec_scientific.test.ts
  • test/runtime_http.test.ts
  • test/runtime_node.test.ts
  • test/runtime_pyodide.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/safe-codec.test.ts
  • test/transport.test.ts
  • test/validators.test.ts
  • test/worker-pool.test.ts
🧰 Additional context used
🧠 Learnings (20)
📓 Common learnings
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.
📚 Learning: 2026-01-19T21:00:30.825Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/adversarial_playground.test.ts:339-360
Timestamp: 2026-01-19T21:00:30.825Z
Learning: In adversarial tests for the bridge (test/adversarial_playground.test.ts), recovery assertions should be kept in the finally block adjacent to cleanup to verify resilience under stress, even if that means post-timeout checks might mask the original error. The goal is to surface recovery failures as legitimate test failures.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/arch-stories.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/bounded-context.test.ts
  • test/code_generation_quality.test.ts
  • test/parallel-processor.test.ts
  • test/transport.test.ts
  • test/integration.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • test/optimized-node.test.ts
  • test/worker-pool.test.ts
  • test/runtime_http.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-19T21:00:52.689Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/fixtures/out_of_order_bridge.py:29-48
Timestamp: 2026-01-19T21:00:52.689Z
Learning: Policy for adversarial test fixtures: adversarial fixtures must preserve the failure mode they exercise, even if that behavior would be "bad" in real code. When reviewing test fixtures with unusual patterns (e.g., missing error handling, intentional protocol violations, unanswered requests), ask for clarifying comments about the intent rather than suggesting normalization or fixes.

Applied to files:

  • test/adversarial_playground.test.ts
📚 Learning: 2026-01-19T21:14:40.872Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/code_generation_quality.test.ts
  • test/runtime_codec_scientific.test.ts
  • src/index.ts
  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • test/integration.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • test/optimized-node.test.ts
  • test/runtime_http.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-20T16:01:14.323Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:599-602
Timestamp: 2026-01-20T16:01:14.323Z
Learning: In `src/runtime/node-bridge.ts` (NodeBridge), a test message is sent to the Python subprocess to confirm the bridge is responsive before marking it as ready.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/bounded-context.test.ts
  • test/discovery.test.ts
  • test/code_generation_quality.test.ts
  • test/runtime_pyodide.test.ts
  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • test/transport.test.ts
  • test/integration.test.ts
  • test/runtime_node.test.ts
  • test/optimized-node.test.ts
  • test/runtime_http.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-19T21:00:52.689Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/fixtures/out_of_order_bridge.py:29-48
Timestamp: 2026-01-19T21:00:52.689Z
Learning: In `test/fixtures/out_of_order_bridge.py`, the fixture intentionally leaves a pending request unanswered at EOF to simulate missing/out-of-order responses and validate bridge behavior when requests never complete; this is the exact failure mode being tested and must be preserved.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/transport.test.ts
  • test/runtime_node.test.ts
  • test/runtime_http.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-20T16:00:49.829Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:203-211
Timestamp: 2026-01-20T16:00:49.829Z
Learning: In the BridgeProtocol implementation (tywrap), reject Map and Set explicitly before serialization (e.g., in safeStringify or the serialization entrypoint) with a clear error like "Bridge protocol does not support Map/Set values". Do not rely on post-hoc checks of non-string keys at the point of JSON.stringify, since Maps/Sets cannot round-trip. This should be enforced at the boundary where data is prepared for serialization to ensure deterministic errors and prevent invalid data from propagating.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/config_loading.test.ts
  • test/arch-stories.test.ts
  • src/utils/ir-cache.ts
  • test/process_utils_timeout.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/generator.test.ts
  • test/bounded-context.test.ts
  • src/core/annotation-parser.ts
  • src/runtime/errors.ts
  • src/types/global.d.ts
  • test/discovery.test.ts
  • src/core/discovery.ts
  • test/code_generation_quality.test.ts
  • test/runtime_codec_scientific.test.ts
  • src/types/index.ts
  • src/index.ts
  • src/utils/cache.ts
  • test/parallel-processor.test.ts
  • src/utils/codec.ts
  • src/runtime/http.ts
  • test/runtime_pyodide.test.ts
  • src/runtime/optimized-node.ts
  • src/runtime/http-io.ts
  • src/runtime/worker-pool.ts
  • test/transport.test.ts
  • test/analyzer.test.ts
  • src/runtime/process-io.ts
  • test/integration.test.ts
  • test/disposable.test.ts
  • src/runtime/node.ts
  • src/runtime/pyodide-io.ts
  • test/validators.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • src/runtime/safe-codec.ts
  • src/cli.ts
  • test/annotation-parser.test.ts
  • test/optimized-node.test.ts
  • test/worker-pool.test.ts
  • test/ir_cache_filename.test.ts
  • src/core/generator.ts
  • test/cache_generateKey.test.ts
  • src/config/index.ts
  • test/runtime_http.test.ts
  • src/utils/parallel-processor.ts
  • test/cli.test.ts
  • src/utils/runtime.ts
  • src/core/analyzer.ts
  • test/bridge-protocol.test.ts
  • src/tywrap.ts
📚 Learning: 2026-01-19T21:48:27.823Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/runtime_bridge_fixtures.test.ts:59-66
Timestamp: 2026-01-19T21:48:27.823Z
Learning: In fixture-based tests (e.g., test/runtime_bridge_fixtures.test.ts) and similar tests in the tywrap repository, prefer early returns when Python or fixture files are unavailable. Do not rely on Vitest dynamic skip APIs; a silent pass is intentional for environments lacking Python/fixtures. Treat missing fixtures as optional soft-guards and ensure the test remains non-disruptive in non-availability scenarios.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/config_loading.test.ts
  • test/arch-stories.test.ts
  • test/process_utils_timeout.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/generator.test.ts
  • test/bounded-context.test.ts
  • test/discovery.test.ts
  • test/code_generation_quality.test.ts
  • test/runtime_codec_scientific.test.ts
  • test/parallel-processor.test.ts
  • test/runtime_pyodide.test.ts
  • test/transport.test.ts
  • test/analyzer.test.ts
  • test/integration.test.ts
  • test/disposable.test.ts
  • test/validators.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • test/annotation-parser.test.ts
  • test/optimized-node.test.ts
  • test/worker-pool.test.ts
  • test/ir_cache_filename.test.ts
  • test/cache_generateKey.test.ts
  • test/runtime_http.test.ts
  • test/cli.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-20T01:34:07.064Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:444-458
Timestamp: 2026-01-20T01:34:07.064Z
Learning: When reviewing promise-based polling patterns (e.g., recursive setTimeout in waitForAvailableWorker functions), ensure that any recursive timer is tracked and cleared on timeout or promise resolution to prevent timer leaks. Do not rely on no-op checks after settlement; verify clearTimeout is invoked in all paths and consider using an explicit cancellation (e.g., AbortController) or a guard to cancel pending timers when the promise settles.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/config_loading.test.ts
  • test/arch-stories.test.ts
  • src/utils/ir-cache.ts
  • test/process_utils_timeout.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/generator.test.ts
  • test/bounded-context.test.ts
  • src/core/annotation-parser.ts
  • src/runtime/errors.ts
  • src/types/global.d.ts
  • test/discovery.test.ts
  • src/core/discovery.ts
  • test/code_generation_quality.test.ts
  • test/runtime_codec_scientific.test.ts
  • src/types/index.ts
  • src/index.ts
  • src/utils/cache.ts
  • test/parallel-processor.test.ts
  • src/utils/codec.ts
  • src/runtime/http.ts
  • test/runtime_pyodide.test.ts
  • src/runtime/optimized-node.ts
  • src/runtime/http-io.ts
  • src/runtime/worker-pool.ts
  • test/transport.test.ts
  • test/analyzer.test.ts
  • src/runtime/process-io.ts
  • test/integration.test.ts
  • test/disposable.test.ts
  • src/runtime/node.ts
  • src/runtime/pyodide-io.ts
  • test/validators.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • src/runtime/safe-codec.ts
  • src/cli.ts
  • test/annotation-parser.test.ts
  • test/optimized-node.test.ts
  • test/worker-pool.test.ts
  • test/ir_cache_filename.test.ts
  • src/core/generator.ts
  • test/cache_generateKey.test.ts
  • src/config/index.ts
  • test/runtime_http.test.ts
  • src/utils/parallel-processor.ts
  • test/cli.test.ts
  • src/utils/runtime.ts
  • src/core/analyzer.ts
  • test/bridge-protocol.test.ts
  • src/tywrap.ts
📚 Learning: 2026-01-20T18:37:05.670Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: src/runtime/process-io.ts:37-40
Timestamp: 2026-01-20T18:37:05.670Z
Learning: In the tywrap repo, ESLint is used for linting (not Biome). Do not flag regex literals that contain control characters (e.g., \u001b, \u0000-\u001F) as lint errors, since the current ESLint configuration allows them. When reviewing TypeScript files (e.g., src/**/*.ts), rely on ESLint results and avoid raising issues about these specific control-character regex literals unless there is a competing config change or a policy requiring explicit escaping.

Applied to files:

  • test/adversarial_playground.test.ts
  • test/config_loading.test.ts
  • test/arch-stories.test.ts
  • src/utils/ir-cache.ts
  • test/process_utils_timeout.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • test/generator.test.ts
  • test/bounded-context.test.ts
  • src/core/annotation-parser.ts
  • src/runtime/errors.ts
  • src/types/global.d.ts
  • test/discovery.test.ts
  • src/core/discovery.ts
  • test/code_generation_quality.test.ts
  • test/runtime_codec_scientific.test.ts
  • src/types/index.ts
  • src/index.ts
  • src/utils/cache.ts
  • test/parallel-processor.test.ts
  • src/utils/codec.ts
  • src/runtime/http.ts
  • test/runtime_pyodide.test.ts
  • src/runtime/optimized-node.ts
  • src/runtime/http-io.ts
  • src/runtime/worker-pool.ts
  • test/transport.test.ts
  • test/analyzer.test.ts
  • src/runtime/process-io.ts
  • test/integration.test.ts
  • test/disposable.test.ts
  • src/runtime/node.ts
  • src/runtime/pyodide-io.ts
  • test/validators.test.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • src/runtime/safe-codec.ts
  • src/cli.ts
  • test/annotation-parser.test.ts
  • test/optimized-node.test.ts
  • test/worker-pool.test.ts
  • test/ir_cache_filename.test.ts
  • src/core/generator.ts
  • test/cache_generateKey.test.ts
  • src/config/index.ts
  • test/runtime_http.test.ts
  • src/utils/parallel-processor.ts
  • test/cli.test.ts
  • src/utils/runtime.ts
  • src/core/analyzer.ts
  • test/bridge-protocol.test.ts
  • src/tywrap.ts
📚 Learning: 2026-01-19T21:48:45.693Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.

Applied to files:

  • test/arch-stories.test.ts
  • test/safe-codec-arrow-special-floats.test.ts
  • src/runtime/errors.ts
  • src/index.ts
  • src/runtime/worker-pool.ts
  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • src/runtime/process-io.ts
  • test/integration.test.ts
  • src/runtime/pyodide-io.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
  • src/runtime/safe-codec.ts
  • test/optimized-node.test.ts
  • test/runtime_http.test.ts
  • src/utils/runtime.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-20T16:00:49.738Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:168-172
Timestamp: 2026-01-20T16:00:49.738Z
Learning: In the tywrap project's BridgeProtocol SafeCodec implementation, Arrow format decoders can produce NaN/Infinity values from binary representations even when the raw JSON payload doesn't contain them. This is why validation for special floats must occur both before encoding (to reject invalid inputs) and after applying decoders (to catch values introduced during Arrow deserialization), protecting downstream consumers from unexpected NaN/Infinity values.

Applied to files:

  • test/safe-codec-arrow-special-floats.test.ts
  • test/safe-codec.test.ts
  • src/runtime/safe-codec.ts
  • test/runtime_http.test.ts
  • test/bridge-protocol.test.ts
📚 Learning: 2026-01-19T21:14:29.869Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:29.869Z
Learning: In the runtime env-var parsing (e.g., in src/runtime/bridge-core.ts and similar modules), adopt a tolerant, best-effort policy: numeric env values should parse by taking the leading numeric portion (e.g., TYWRAP_CODEC_MAX_BYTES=1024abc -> 1024). Only reject clearly invalid values (non-numeric start or <= 0). This reduces surprising failures from minor typos. Add tests to cover partial numeric prefixes, and ensure downstream logic documents the trusted range; consider a small helper to extract a positive integer from a string with a safe fallback.

Applied to files:

  • src/runtime/errors.ts
  • src/runtime/http.ts
  • src/runtime/optimized-node.ts
  • src/runtime/http-io.ts
  • src/runtime/worker-pool.ts
  • src/runtime/process-io.ts
  • src/runtime/node.ts
  • src/runtime/pyodide-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:14:35.390Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:35.390Z
Learning: In src/runtime/bridge-core.ts and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (such as runtime, shape, or error-payload checks) inside tight loops unless the protocol boundary is untrusted or there is a concrete bug report. The Python bridge protocol is controlled via tests, so these checks add unnecessary branching overhead without meaningful benefit. Apply this guidance to other hot-path runtime loops in src/runtime/**/*.ts, and re-enable additional validations only when a documented risk or failure scenario is identified. Ensure tests cover protocol validation where applicable.

Applied to files:

  • src/runtime/errors.ts
  • src/runtime/http.ts
  • src/runtime/optimized-node.ts
  • src/runtime/http-io.ts
  • src/runtime/worker-pool.ts
  • src/runtime/process-io.ts
  • src/runtime/node.ts
  • src/runtime/pyodide-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-20T16:01:39.136Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:826-830
Timestamp: 2026-01-20T16:01:39.136Z
Learning: In the tywrap BridgeProtocol architecture (ADR-002), `HttpIO` (and other Transport implementations like `ProcessIO`, `PyodideIO`) implements only the `Transport` interface (methods: `send`, `isReady`, `dispose`). The `RuntimeExecution` interface methods (`call`, `instantiate`, `callMethod`, `disposeInstance`) are implemented by `BridgeProtocol`, which delegates to `Transport` instances. Transport implementations should not include RuntimeExecution methods.

Applied to files:

  • src/index.ts
  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • test/transport.test.ts
  • src/runtime/pyodide-io.ts
📚 Learning: 2026-01-19T21:14:37.032Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.

Applied to files:

  • src/utils/codec.ts
  • test/safe-codec.test.ts
📚 Learning: 2026-01-20T01:36:14.701Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:743-759
Timestamp: 2026-01-20T01:36:14.701Z
Learning: In the tywrap repository, when reviewing concurrent request handling patterns (especially with BridgeCore request multiplexing), flag uses of boolean `busy` flags that should be request counters instead. A boolean flag creates a race condition: when request A completes and sets `busy = false`, request B (still in flight via multiplexing) is incorrectly treated as idle. The fix is to use an `inFlightRequests: number` counter that increments on request start and decrements on completion, then check `worker.inFlightRequests === 0` before cleanup/termination.

Applied to files:

  • src/runtime/worker-pool.ts
📚 Learning: 2026-01-20T01:33:12.841Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:238-249
Timestamp: 2026-01-20T01:33:12.841Z
Learning: In the tywrap repository, when reviewing init/startup patterns, prioritize cases that break core functionality over edge cases with diagnostic-only impact (e.g., missing bridgeInfo metadata). If workers are healthy and operational, defer low-impact defensive checks to follow-up PRs.

Applied to files:

  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
📚 Learning: 2026-01-20T16:01:14.323Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:599-602
Timestamp: 2026-01-20T16:01:14.323Z
Learning: In the tywrap repository, bridges currently use implicit ready detection: the first successful response from the Python subprocess proves the bridge is ready to handle requests.

Applied to files:

  • docs/plans/2026-01-22-tywrapped-ecosystem-vision.md
  • test/runtime_http.test.ts
📚 Learning: 2026-01-20T16:01:29.276Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:636-637
Timestamp: 2026-01-20T16:01:29.276Z
Learning: In the tywrap BridgeProtocol ProcessIO transport (ADR-002), when implementing `restartAfterRequests` functionality, use drain-before-restart as the default: wait for `pendingRequests.size === 0` before killing and restarting the process. This prevents rejecting in-flight requests during restart. Alternative graceful restart (spawn new process, drain old) adds complexity with two concurrent processes.

Applied to files:

  • src/runtime/process-io.ts
  • test/bridge-protocol.test.ts
🧬 Code graph analysis (24)
test/config_loading.test.ts (1)
src/utils/runtime.ts (1)
  • writeFile (386-416)
test/arch-stories.test.ts (2)
src/index.ts (2)
  • BridgeProtocolError (73-73)
  • BridgeExecutionError (76-76)
src/runtime/errors.ts (2)
  • BridgeProtocolError (13-13)
  • BridgeExecutionError (31-33)
src/utils/ir-cache.ts (1)
src/utils/runtime.ts (1)
  • hashUtils (699-727)
test/safe-codec-arrow-special-floats.test.ts (1)
src/runtime/safe-codec.ts (1)
  • SafeCodec (317-614)
test/bounded-context.test.ts (3)
src/utils/runtime.ts (1)
  • resolve (302-320)
src/index.ts (2)
  • BridgeTimeoutError (74-74)
  • BridgeDisposedError (75-75)
src/runtime/errors.ts (2)
  • BridgeTimeoutError (14-14)
  • BridgeDisposedError (15-15)
src/core/annotation-parser.ts (1)
src/types/index.ts (1)
  • PythonType (70-82)
src/runtime/errors.ts (1)
src/index.ts (2)
  • BridgeCodecError (72-72)
  • BridgeError (71-71)
test/parallel-processor.test.ts (1)
src/utils/parallel-processor.ts (1)
  • ParallelProcessor (126-782)
src/runtime/http.ts (1)
src/utils/codec.ts (1)
  • autoRegisterArrowDecoder (165-194)
src/runtime/process-io.ts (2)
src/index.ts (2)
  • BridgeProtocolError (73-73)
  • BridgeTimeoutError (74-74)
src/runtime/errors.ts (2)
  • BridgeProtocolError (13-13)
  • BridgeTimeoutError (14-14)
test/integration.test.ts (1)
src/tywrap.ts (1)
  • generate (112-323)
src/runtime/node.ts (2)
src/index.ts (1)
  • BridgeProtocolError (73-73)
src/runtime/errors.ts (1)
  • BridgeProtocolError (13-13)
test/validators.test.ts (1)
src/runtime/validators.ts (2)
  • assertFiniteNumber (69-75)
  • containsSpecialFloat (206-208)
test/runtime_node.test.ts (1)
src/runtime/errors.ts (1)
  • BridgeExecutionError (31-33)
test/safe-codec.test.ts (2)
src/runtime/errors.ts (1)
  • BridgeCodecError (17-29)
src/runtime/safe-codec.ts (1)
  • SafeCodec (317-614)
src/runtime/safe-codec.ts (3)
src/index.ts (3)
  • BridgeCodecError (72-72)
  • containsSpecialFloat (59-59)
  • BridgeProtocolError (73-73)
src/runtime/errors.ts (2)
  • BridgeCodecError (17-29)
  • BridgeProtocolError (13-13)
src/runtime/validators.ts (1)
  • containsSpecialFloat (206-208)
test/annotation-parser.test.ts (1)
src/core/annotation-parser.ts (1)
  • parseAnnotationToPythonType (7-295)
test/optimized-node.test.ts (2)
src/index.ts (1)
  • BridgeCodecError (72-72)
src/runtime/errors.ts (1)
  • BridgeCodecError (17-29)
test/worker-pool.test.ts (3)
src/index.ts (3)
  • WorkerPoolOptions (40-40)
  • WorkerPool (40-40)
  • BridgeExecutionError (76-76)
src/runtime/worker-pool.ts (2)
  • WorkerPoolOptions (21-42)
  • WorkerPool (101-504)
src/runtime/errors.ts (1)
  • BridgeExecutionError (31-33)
test/ir_cache_filename.test.ts (1)
src/utils/ir-cache.ts (1)
  • computeIrCacheFilename (44-50)
test/runtime_http.test.ts (3)
src/runtime/errors.ts (3)
  • BridgeCodecError (17-29)
  • BridgeExecutionError (31-33)
  • BridgeTimeoutError (14-14)
src/runtime/http.ts (1)
  • HttpBridge (64-93)
src/utils/codec.ts (2)
  • clearArrowDecoder (120-122)
  • hasArrowDecoder (127-129)
test/cli.test.ts (1)
tools/matrix.js (1)
  • config (120-127)
test/bridge-protocol.test.ts (2)
src/runtime/errors.ts (2)
  • BridgeDisposedError (15-15)
  • BridgeCodecError (17-29)
src/runtime/safe-codec.ts (1)
  • SafeCodec (317-614)
src/tywrap.ts (3)
src/utils/runtime.ts (3)
  • processUtils (422-617)
  • isWindows (626-632)
  • pathUtils (261-342)
src/core/annotation-parser.ts (1)
  • parseAnnotationToPythonType (7-295)
src/utils/ir-cache.ts (1)
  • computeIrCacheFilename (44-50)
🪛 GitHub Check: lint
src/core/generator.ts

[warning] 122-122:
Missing return type on function


[warning] 170-170:
Missing return type on function


[warning] 249-249:
Missing return type on function


[warning] 721-721:
'requiredPosCount' is already declared in the upper scope on line 677 column 13


[warning] 690-690:
Missing return type on function


[warning] 657-657:
Missing return type on function


[warning] 535-535:
'requiredPosCount' is already declared in the upper scope on line 486 column 15


[warning] 499-499:
Missing return type on function

src/core/analyzer.ts

[warning] 663-663:
Variable Assigned to Object Injection Sink

src/tywrap.ts

[warning] 193-193:
Found non-literal argument to RegExp Constructor

🪛 Ruff (0.15.0)
runtime/python_bridge.py

[warning] 147-147: Missing return type annotation for private function _deserialize_bytes_envelope

(ANN202)


[warning] 164-164: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 168-168: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 173-173: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 177-177: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 662-662: Avoid specifying long messages outside the exception class

(TRY003)

Comment on lines 238 to 260
if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
const parts = splitTopLevel(inner, ',');
if (parts.length >= 2) {
const paramsPart = (parts[0] ?? '').trim();
const returnPart = parts.slice(1).join(',').trim();
const paramInner =
paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
const paramTypes = ((): PythonType[] => {
// Callable[..., R] uses a top-level Ellipsis.
if (paramsPart === '...' || paramsPart === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
const trimmed = paramInner.trim();
if (trimmed === '...' || trimmed === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
return trimmed ? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1)) : [];
})();
const returnType = parse(returnPart, depth + 1);
return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent indentation in the Callable parsing block.

Lines 238–260 use tab-based indentation while the rest of the file uses spaces. This won't affect behavior but is a formatting inconsistency that linting should catch.

Proposed fix (align with file's space-based indentation)
-	    if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
-	      const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
-	      const parts = splitTopLevel(inner, ',');
-	      if (parts.length >= 2) {
-	        const paramsPart = (parts[0] ?? '').trim();
-	        const returnPart = parts.slice(1).join(',').trim();
-	        const paramInner =
-	          paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
-	        const paramTypes = ((): PythonType[] => {
-	          // Callable[..., R] uses a top-level Ellipsis.
-	          if (paramsPart === '...' || paramsPart === 'Ellipsis') {
-	            return [{ kind: 'custom', name: '...' } as PythonType];
-	          }
-	          const trimmed = paramInner.trim();
-	          if (trimmed === '...' || trimmed === 'Ellipsis') {
-	            return [{ kind: 'custom', name: '...' } as PythonType];
-	          }
-	          return trimmed ? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1)) : [];
-	        })();
-	        const returnType = parse(returnPart, depth + 1);
-	        return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
-	      }
-	    }
+    if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
+      const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
+      const parts = splitTopLevel(inner, ',');
+      if (parts.length >= 2) {
+        const paramsPart = (parts[0] ?? '').trim();
+        const returnPart = parts.slice(1).join(',').trim();
+        const paramInner =
+          paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
+        const paramTypes = ((): PythonType[] => {
+          // Callable[..., R] uses a top-level Ellipsis.
+          if (paramsPart === '...' || paramsPart === 'Ellipsis') {
+            return [{ kind: 'custom', name: '...' } as PythonType];
+          }
+          const trimmed = paramInner.trim();
+          if (trimmed === '...' || trimmed === 'Ellipsis') {
+            return [{ kind: 'custom', name: '...' } as PythonType];
+          }
+          return trimmed ? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1)) : [];
+        })();
+        const returnType = parse(returnPart, depth + 1);
+        return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
const parts = splitTopLevel(inner, ',');
if (parts.length >= 2) {
const paramsPart = (parts[0] ?? '').trim();
const returnPart = parts.slice(1).join(',').trim();
const paramInner =
paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
const paramTypes = ((): PythonType[] => {
// Callable[..., R] uses a top-level Ellipsis.
if (paramsPart === '...' || paramsPart === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
const trimmed = paramInner.trim();
if (trimmed === '...' || trimmed === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
return trimmed ? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1)) : [];
})();
const returnType = parse(returnPart, depth + 1);
return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
}
}
if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
const parts = splitTopLevel(inner, ',');
if (parts.length >= 2) {
const paramsPart = (parts[0] ?? '').trim();
const returnPart = parts.slice(1).join(',').trim();
const paramInner =
paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
const paramTypes = ((): PythonType[] => {
// Callable[..., R] uses a top-level Ellipsis.
if (paramsPart === '...' || paramsPart === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
const trimmed = paramInner.trim();
if (trimmed === '...' || trimmed === 'Ellipsis') {
return [{ kind: 'custom', name: '...' } as PythonType];
}
return trimmed ? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1)) : [];
})();
const returnType = parse(returnPart, depth + 1);
return { kind: 'callable', parameters: paramTypes, returnType } as PythonType;
}
}
🤖 Prompt for AI Agents
In `@src/core/annotation-parser.ts` around lines 238 - 260, The Callable parsing
block uses tab indentation causing a style inconsistency; locate the block that
begins with the raw.startsWith('typing.Callable[') ||
raw.startsWith('Callable[') check (inside the parse function where splitTopLevel
and parse are used) and replace the tab-based indentation with the file's
standard space-based indentation to match surrounding lines and satisfy
linting/style rules.

Comment on lines 533 to 537
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
const requiredPosCount =
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Variable requiredPosCount shadows outer declaration (static analysis warning).

requiredPosCount on line 535 shadows the declaration on line 486. Same issue occurs at line 721 vs 677 in the constructor spec. While not a bug (different block scopes), it hurts readability and triggers lint warnings. Use a distinct name (e.g., overloadRequiredPosCount) inside the overload-generation blocks.

🧰 Tools
🪛 GitHub Check: lint

[warning] 535-535:
'requiredPosCount' is already declared in the upper scope on line 486 column 15

🤖 Prompt for AI Agents
In `@src/core/generator.ts` around lines 533 - 537, The variable requiredPosCount
declared inside the overload-generation blocks shadows an outer variable of the
same name; update the inner declaration(s) (the one inside the block that uses
needsKwargsParam, requiredKwOnlyNames and positionalParams, and the analogous
declaration in the constructor spec overload-generation) to a distinct name such
as overloadRequiredPosCount to avoid shadowing and lint warnings, and update all
references within those blocks to use the new name.

Comment on lines 44 to 49
export async function computeIrCacheFilename(keyObject: IrCacheKeyObject): Promise<string> {
// Canonicalize to avoid cache misses when key object property insertion order differs.
const normalized = JSON.stringify(stableSortForJson(keyObject));
const digest = await hashUtils.sha256Hex(normalized);
// Hash-only filename: safe ASCII, no separators, stable length.
return `ir_${digest.slice(0, 32)}.json`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

digest.slice(0, 32) would collapse to all zeros under the DJB2 fallback in hashUtils.sha256Hex.

The DJB2 fallback (in src/utils/runtime.ts) produces a 32-bit hash left-padded to 64 hex chars. Slicing the first 32 characters yields "00000000000000000000000000000000" for every input, causing total cache-key collisions. In practice this path is very unlikely (requires no Node crypto and no Web Crypto), but if it's ever hit, every IR file would map to the same filename.

Consider slicing the last 32 chars (the meaningful portion) or, better, always slicing a centered/tail window:

Proposed fix
-  return `ir_${digest.slice(0, 32)}.json`;
+  // Use the tail of the digest so that the DJB2 non-crypto fallback
+  // (which left-pads with '0') still yields distinct filenames.
+  return `ir_${digest.slice(-32)}.json`;
🤖 Prompt for AI Agents
In `@src/utils/ir-cache.ts` around lines 44 - 49, computeIrCacheFilename is
currently using digest.slice(0, 32) which collapses to all zeros when
hashUtils.sha256Hex falls back to the 32-bit DJB2 implementation (padded to 64
hex chars); update computeIrCacheFilename to use the meaningful portion of the
digest (for example digest.slice(-32) to take the last 32 hex chars) so
filenames differ under the DJB2 fallback, ensuring the change is applied where
computeIrCacheFilename constructs the filename and that it still produces a
stable ASCII filename like `ir_<hex>.json`.

Comment on lines 718 to 727
// Fallback to DJB2 (non-crypto) for unknown runtimes
let hash = 5381;
for (let i = 0; i < text.length; i++) {
hash = (hash << 5) + hash + text.charCodeAt(i);
hash |= 0;
}
// Match sha256 hex shape (64 chars) so callers can rely on fixed-length output.
return Math.abs(hash).toString(16).padStart(64, '0');
},
};
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Tab/space indentation mismatch in the DJB2 fallback block.

Lines 718–727 use tab characters while the rest of the file uses spaces. This won't break functionality but will cause issues if a formatter or linter enforces consistent indentation. The closing braces on lines 726–727 appear to close correctly (sha256Hex method then hashUtils object), but the tab indentation makes this hard to verify visually.

🤖 Prompt for AI Agents
In `@src/utils/runtime.ts` around lines 718 - 727, Replace tab characters in the
DJB2 fallback block inside the sha256Hex function with the project's space-based
indentation to match the rest of the file; specifically update the lines that
initialize hash, the for-loop, the hash calculations, and the return so they use
spaces, and verify the closing braces for sha256Hex and hashUtils align with
surrounding code so the function and object close visually and syntactically
consistent with other methods.

Comment on lines 696 to 720
it('dispose() rejects pending tasks', async () => {
const workerScript = await writeWorkerScript();
processor = new ParallelProcessor({
maxWorkers: 1,
workerScript,
enableCaching: false,
enableMemoryMonitoring: false,
retryAttempts: 1,
taskTimeout: 10_000,
});

const promise = processor.executeTasks([
{ id: 'hang', type: 'custom', data: { action: 'hang' } },
]);

// Give the worker a moment to receive the task.
await new Promise(resolve => setTimeout(resolve, 50));
await processor.dispose();

const results = await promise;
expect(results).toHaveLength(1);
expect(results[0]?.success).toBe(false);
expect(String(results[0]?.error ?? '').toLowerCase()).toContain('disposed');
expect(processor.getStats().activeTasks).toBe(0);
}, 10_000);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timing-dependent test: the 50ms delay may be flaky under CI load.

Line 712 uses a fixed 50ms delay to ensure the worker receives the task before dispose() is called. Under heavy CI load, this could be insufficient, causing the task to be rejected before it reaches the worker (different failure mode than intended).

Consider polling processor.getStats().activeTasks to confirm the task is in-flight before disposing, or increase the delay with a comment explaining the rationale.

🤖 Prompt for AI Agents
In `@test/parallel-processor.test.ts` around lines 696 - 720, The test's fixed
50ms sleep is flaky; replace it with a small polling loop that waits until
processor.getStats().activeTasks > 0 (or a short timeout like 1s) before calling
processor.dispose(), so you ensure the task is actually in-flight; update the it
block around the executeTasks/await processor.dispose() sequence to poll
getStats().activeTasks (or fall back to a longer delay with a comment) to avoid
CI timing issues while preserving the existing test timeout.

Comment on lines 7 to 19
describe('processUtils.exec', () => {
it('rejects with a clear timeout error when the subprocess hangs', async () => {
const pythonPath = getDefaultPythonPath();
const check = spawnSync(pythonPath, ['--version'], { encoding: 'utf-8' });
if (check.error || check.status !== 0) {
return; // Python not available
}
const timeoutMs = 200;

await expect(
processUtils.exec(pythonPath, ['-c', 'import time; time.sleep(999)'], { timeoutMs })
).rejects.toThrow(`timed out after ${timeoutMs}ms`);
}, 10_000);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Well-structured test, consistent with project conventions for Python-optional guards.

The early return on missing Python aligns with the project's soft-skip approach. One minor hardening suggestion: the spawnSync availability check on line 10 has no timeout of its own—if the Python binary were present but hung, this blocks until the outer 10 s Vitest timeout kicks in. Adding a short timeout makes the guard self-contained.

🛡️ Optional: add timeout to the availability probe
-    const check = spawnSync(pythonPath, ['--version'], { encoding: 'utf-8' });
+    const check = spawnSync(pythonPath, ['--version'], { encoding: 'utf-8', timeout: 5_000 });

Based on learnings, the early-return pattern for missing Python is the preferred convention ("prefer early returns when Python or fixture files are unavailable … a silent pass is intentional for environments lacking Python/fixtures").

🤖 Prompt for AI Agents
In `@test/process_utils_timeout.test.ts` around lines 7 - 19, The availability
probe uses spawnSync(pythonPath, ['--version'], { encoding: 'utf-8' }) without a
timeout, so if the Python binary hangs the test blocks; update that probe call
in test/process_utils_timeout.test.ts (the spawnSync check near
getDefaultPythonPath() and timeoutMs) to include a short timeout option (e.g.,
~1000ms) in its options so the guard self-contains its failure path and returns
early instead of relying on the outer Vitest timeout.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f479efc79f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 163 to 164
if not isinstance(b64, str) or not b64:
raise ProtocolError('Invalid bytes envelope: missing b64')

Choose a reason for hiding this comment

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

P1 Badge Accept empty byte envelopes during request deserialization

The new envelope validator treats b64: "" / data: "" as missing (or not b64 / or not data), but empty base64 is the correct encoding for b'' / new Uint8Array(0). Calls that pass zero-length binary arguments now raise ProtocolError instead of reaching the target Python function, so bytes round-trip still fails for a valid and common edge case.

Useful? React with 👍 / 👎.

if not isinstance(b64, str) or not b64:
raise ProtocolError('Invalid bytes envelope: missing b64')
try:
return base64.b64decode(b64)

Choose a reason for hiding this comment

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

P2 Badge Validate base64 strictly before decoding byte envelopes

base64.b64decode(...) is called without strict validation, so malformed envelopes (for example b64: "%%%") can decode to empty bytes instead of erroring. This silently corrupts binary inputs at the transport boundary (the same decode pattern is also used in the legacy data branch), making payload corruption difficult to detect.

Useful? React with 👍 / 👎.

@bbopen bbopen force-pushed the codex/tywrap-open-issue-reliability-pass branch from f479efc to 7b5137b Compare February 16, 2026 19:29
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/safe-codec.test.ts`:
- Around line 360-368: Add a companion test that covers the alternative envelope
shape handled by the reviver: create a payload JSON with result: {
__tywrap_bytes__: true, b64: 'SGVsbG8=' }, call
codec.decodeResponse<Uint8Array>(payload), and assert the return is a Uint8Array
and its bytes equal [72,101,108,108,111]; this mirrors the existing test that
uses { __type__: 'bytes' } and ensures encodeRequest/decoding paths for
__tywrap_bytes__ are verified.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f479efc and 7b5137b.

📒 Files selected for processing (5)
  • runtime/python_bridge.py
  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:599-602
Timestamp: 2026-01-20T16:01:14.323Z
Learning: In `src/runtime/node-bridge.ts` (NodeBridge), a test message is sent to the Python subprocess to confirm the bridge is responsive before marking it as ready.
📚 Learning: 2026-01-19T21:48:27.823Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/runtime_bridge_fixtures.test.ts:59-66
Timestamp: 2026-01-19T21:48:27.823Z
Learning: In fixture-based tests (e.g., test/runtime_bridge_fixtures.test.ts) and similar tests in the tywrap repository, prefer early returns when Python or fixture files are unavailable. Do not rely on Vitest dynamic skip APIs; a silent pass is intentional for environments lacking Python/fixtures. Treat missing fixtures as optional soft-guards and ensure the test remains non-disruptive in non-availability scenarios.

Applied to files:

  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-20T01:34:07.064Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:444-458
Timestamp: 2026-01-20T01:34:07.064Z
Learning: When reviewing promise-based polling patterns (e.g., recursive setTimeout in waitForAvailableWorker functions), ensure that any recursive timer is tracked and cleared on timeout or promise resolution to prevent timer leaks. Do not rely on no-op checks after settlement; verify clearTimeout is invoked in all paths and consider using an explicit cancellation (e.g., AbortController) or a guard to cancel pending timers when the promise settles.

Applied to files:

  • test/safe-codec.test.ts
  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-20T18:37:05.670Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: src/runtime/process-io.ts:37-40
Timestamp: 2026-01-20T18:37:05.670Z
Learning: In the tywrap repo, ESLint is used for linting (not Biome). Do not flag regex literals that contain control characters (e.g., \u001b, \u0000-\u001F) as lint errors, since the current ESLint configuration allows them. When reviewing TypeScript files (e.g., src/**/*.ts), rely on ESLint results and avoid raising issues about these specific control-character regex literals unless there is a competing config change or a policy requiring explicit escaping.

Applied to files:

  • test/safe-codec.test.ts
  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-20T16:00:49.829Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:203-211
Timestamp: 2026-01-20T16:00:49.829Z
Learning: In the BridgeProtocol implementation (tywrap), reject Map and Set explicitly before serialization (e.g., in safeStringify or the serialization entrypoint) with a clear error like "Bridge protocol does not support Map/Set values". Do not rely on post-hoc checks of non-string keys at the point of JSON.stringify, since Maps/Sets cannot round-trip. This should be enforced at the boundary where data is prepared for serialization to ensure deterministic errors and prevent invalid data from propagating.

Applied to files:

  • test/safe-codec.test.ts
  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-19T21:14:29.869Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:29.869Z
Learning: In the runtime env-var parsing (e.g., in src/runtime/bridge-core.ts and similar modules), adopt a tolerant, best-effort policy: numeric env values should parse by taking the leading numeric portion (e.g., TYWRAP_CODEC_MAX_BYTES=1024abc -> 1024). Only reject clearly invalid values (non-numeric start or <= 0). This reduces surprising failures from minor typos. Add tests to cover partial numeric prefixes, and ensure downstream logic documents the trusted range; consider a small helper to extract a positive integer from a string with a safe fallback.

Applied to files:

  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:14:35.390Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:35.390Z
Learning: In src/runtime/bridge-core.ts and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (such as runtime, shape, or error-payload checks) inside tight loops unless the protocol boundary is untrusted or there is a concrete bug report. The Python bridge protocol is controlled via tests, so these checks add unnecessary branching overhead without meaningful benefit. Apply this guidance to other hot-path runtime loops in src/runtime/**/*.ts, and re-enable additional validations only when a documented risk or failure scenario is identified. Ensure tests cover protocol validation where applicable.

Applied to files:

  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:48:45.693Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.

Applied to files:

  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-20T16:01:14.323Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:599-602
Timestamp: 2026-01-20T16:01:14.323Z
Learning: In `src/runtime/node-bridge.ts` (NodeBridge), a test message is sent to the Python subprocess to confirm the bridge is responsive before marking it as ready.

Applied to files:

  • test/runtime_node.test.ts
📚 Learning: 2026-01-19T21:14:40.872Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.

Applied to files:

  • test/runtime_node.test.ts
  • runtime/python_bridge.py
🧬 Code graph analysis (3)
test/safe-codec.test.ts (1)
test/python/test_safe_codec.py (1)
  • codec (35-37)
src/runtime/safe-codec.ts (1)
src/runtime/errors.ts (1)
  • BridgeCodecError (17-29)
test/runtime_node.test.ts (1)
scripts/act.mjs (1)
  • output (59-59)
🪛 Ruff (0.15.0)
runtime/python_bridge.py

[warning] 147-147: Missing return type annotation for private function _deserialize_bytes_envelope

(ANN202)


[warning] 164-164: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 168-168: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 173-173: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 177-177: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: codec-suite
🔇 Additional comments (6)
src/runtime/process-io.ts (1)

736-739: LGTM — defensive unref() guard.

The runtime check ensures compatibility with environments where setTimeout doesn't return a Node.js Timeout object with an unref method.

runtime/python_bridge.py (2)

182-198: LGTM — recursive deserialization is clean and correct.

The sentinel-based approach avoids ambiguity between "decoded to None" and "not a bytes envelope". The recursive walk over lists and dicts correctly propagates deserialization to nested values. Since JSON-parsed data is acyclic, no cycle guard is needed.


707-708: LGTM — consistent integration across all three handler entry points.

deserialize(coerce_list(...)) and deserialize(coerce_dict(...)) are applied uniformly to handle_call, handle_instantiate, and handle_call_method, matching the PR objective.

Also applies to: 718-719, 731-732

src/runtime/safe-codec.ts (2)

336-350: LGTM — fromBase64 correctly handles both Node and browser runtimes.

The Buffer.from path correctly slices with byteOffset and length to avoid issues with Node's pooled ArrayBuffer. The atob fallback covers non-Node environments, and the error for unsupported runtimes is clear.


359-374: Asymmetry is intentional but deserves a comment explaining the design.

bytesHandling gates encoding in encodeRequest but reviveValue unconditionally decodes bytes envelopes during response parsing. This is by design—bytesHandling: 'reject' prevents us from sending binary data, but we must always accept bytes from responses since we cannot control what the remote side returns.

However, this differs from rejectSpecialFloats, which is validated on both encode and decode. Consider adding a comment to reviveValue explaining why decode ignores bytesHandling to clarify this intentional asymmetry. Also add a test that verifies a codec with bytesHandling: 'reject' can still decode bytes from responses.

test/runtime_node.test.ts (1)

115-131: Good end-to-end regression test for the bytes round-trip.

This exercises the full pipeline: JS Uint8Array → base64 envelope → Python bytes → response envelope → JS Uint8Array. The len assertion confirms Python receives real bytes (not a dict), and the bytes call confirms the return path.

Copy link
Owner Author

bbopen commented Feb 16, 2026

Requesting full automated reviews on the cleaned PR history after the branch reset.

@codex review
@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

@bbopen, I'll perform a full automated review of the cleaned PR history for PR #194.


🧠 Learnings used
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.

Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.

Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.
✅ Actions performed

Full review triggered.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b5137b489

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 163 to 164
if not isinstance(b64, str) or not b64:
raise ProtocolError('Invalid bytes envelope: missing b64')

Choose a reason for hiding this comment

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

P1 Badge Accept empty base64 payloads in bytes envelopes

The new bytes deserializer rejects empty strings (not b64 / not data) as “missing”, but empty binary payloads are valid and are encoded as "" by both JS (Uint8Array(0) via toString('base64')) and Python (base64.b64encode(b'').decode('ascii')). This makes legitimate zero-length byte arguments fail with ProtocolError instead of round-tripping, so calls that pass empty blobs now break at the JS↔Python boundary.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/runtime/safe-codec.ts (1)

508-517: ⚠️ Potential issue | 🟡 Minor

Base64 decode errors will surface as misleading "JSON parse failed" messages.

If fromBase64 throws inside the reviver (e.g., malformed base64 in a bytes envelope), the error propagates out of JSON.parse and is caught here as a "JSON parse failed" error. The JSON itself was valid — the real issue is base64 decoding. Consider catching base64 errors in reviveValue and wrapping them with a clearer message.

Proposed fix
   private reviveValue(_key: string, value: unknown): unknown {
     if (value === null || typeof value !== 'object' || Array.isArray(value)) {
       return value;
     }
     const obj = value as Record<string, unknown>;

     if (obj.__tywrap_bytes__ === true && typeof obj.b64 === 'string') {
-      return this.fromBase64(obj.b64);
+      try {
+        return this.fromBase64(obj.b64);
+      } catch (err) {
+        throw new BridgeCodecError(
+          `Failed to decode bytes envelope: ${err instanceof Error ? err.message : String(err)}`,
+          { codecPhase: 'decode', valueType: 'bytes' }
+        );
+      }
     }

     if (obj.__type__ === 'bytes' && obj.encoding === 'base64' && typeof obj.data === 'string') {
-      return this.fromBase64(obj.data);
+      try {
+        return this.fromBase64(obj.data);
+      } catch (err) {
+        throw new BridgeCodecError(
+          `Failed to decode bytes envelope: ${err instanceof Error ? err.message : String(err)}`,
+          { codecPhase: 'decode', valueType: 'bytes' }
+        );
+      }
     }

     return value;
   }

The BridgeCodecError thrown here would then be caught by the outer catch block but re-thrown as-is (line 463-464 pattern), providing a clear error rather than "JSON parse failed".

Wait — actually, looking at the catch block (lines 511-517), it does not re-throw BridgeCodecError — it catches all errors uniformly and wraps them. This differs from encodeRequest (lines 462-468) which checks for BridgeCodecError and BridgeProtocolError first. The decode catch block should do the same.

Additional fix in decodeResponse catch block (and same for decodeResponseAsync)
     } catch (err) {
+      if (err instanceof BridgeCodecError) {
+        throw err;
+      }
       const errorMessage = err instanceof Error ? err.message : String(err);
       throw new BridgeCodecError(
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/runtime/safe-codec.ts` around lines 508 - 517, The JSON parse catch is
masking errors thrown by the reviver (e.g., fromBase64) as "JSON parse failed";
update the decode path to preserve and rethrow existing
BridgeCodecError/BridgeProtocolError like encodeRequest does: in the try/catch
around JSON.parse (used with reviveValue) detect if the caught error is already
a BridgeCodecError or BridgeProtocolError and rethrow it, otherwise wrap
non-bridge errors in a new BridgeCodecError with the "JSON parse failed" message
and payload snippet; additionally, adjust reviveValue to catch base64/fromBase64
failures and throw a BridgeCodecError with a clear message identifying base64
decode failure so decodeResponse and decodeResponseAsync receive a specific
error to propagate.
🤖 Fix all issues with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@src/runtime/safe-codec.ts`:
- Around line 508-517: The JSON parse catch is masking errors thrown by the
reviver (e.g., fromBase64) as "JSON parse failed"; update the decode path to
preserve and rethrow existing BridgeCodecError/BridgeProtocolError like
encodeRequest does: in the try/catch around JSON.parse (used with reviveValue)
detect if the caught error is already a BridgeCodecError or BridgeProtocolError
and rethrow it, otherwise wrap non-bridge errors in a new BridgeCodecError with
the "JSON parse failed" message and payload snippet; additionally, adjust
reviveValue to catch base64/fromBase64 failures and throw a BridgeCodecError
with a clear message identifying base64 decode failure so decodeResponse and
decodeResponseAsync receive a specific error to propagate.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 620a19e and 7b5137b.

📒 Files selected for processing (5)
  • runtime/python_bridge.py
  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
  • test/runtime_node.test.ts
  • test/safe-codec.test.ts
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: runtime/python_bridge.py:99-123
Timestamp: 2026-01-19T21:49:05.612Z
Learning: In the tywrap repository, TYWRAP_REQUEST_MAX_BYTES uses strict integer parsing that rejects values with trailing characters (e.g., "1024abc"). This differs from TYWRAP_CODEC_MAX_BYTES, which uses tolerant/best-effort parsing that accepts numeric prefixes. The strict policy for REQUEST_MAX_BYTES ensures explicit integer values and consistent parse behavior across Node/Python implementations.
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:238-249
Timestamp: 2026-01-20T01:33:12.841Z
Learning: In the tywrap repository, when reviewing init/startup patterns, prioritize cases that break core functionality over edge cases with diagnostic-only impact (e.g., missing bridgeInfo metadata). If workers are healthy and operational, defer low-impact defensive checks to follow-up PRs.
📚 Learning: 2026-01-20T01:34:07.064Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 136
File: src/runtime/node.ts:444-458
Timestamp: 2026-01-20T01:34:07.064Z
Learning: When reviewing promise-based polling patterns (e.g., recursive setTimeout in waitForAvailableWorker functions), ensure that any recursive timer is tracked and cleared on timeout or promise resolution to prevent timer leaks. Do not rely on no-op checks after settlement; verify clearTimeout is invoked in all paths and consider using an explicit cancellation (e.g., AbortController) or a guard to cancel pending timers when the promise settles.

Applied to files:

  • src/runtime/process-io.ts
  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:14:29.869Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:29.869Z
Learning: In the runtime env-var parsing (e.g., in src/runtime/bridge-core.ts and similar modules), adopt a tolerant, best-effort policy: numeric env values should parse by taking the leading numeric portion (e.g., TYWRAP_CODEC_MAX_BYTES=1024abc -> 1024). Only reject clearly invalid values (non-numeric start or <= 0). This reduces surprising failures from minor typos. Add tests to cover partial numeric prefixes, and ensure downstream logic documents the trusted range; consider a small helper to extract a positive integer from a string with a safe fallback.

Applied to files:

  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:14:35.390Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:35.390Z
Learning: In src/runtime/bridge-core.ts and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (such as runtime, shape, or error-payload checks) inside tight loops unless the protocol boundary is untrusted or there is a concrete bug report. The Python bridge protocol is controlled via tests, so these checks add unnecessary branching overhead without meaningful benefit. Apply this guidance to other hot-path runtime loops in src/runtime/**/*.ts, and re-enable additional validations only when a documented risk or failure scenario is identified. Ensure tests cover protocol validation where applicable.

Applied to files:

  • src/runtime/process-io.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-20T18:37:05.670Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: src/runtime/process-io.ts:37-40
Timestamp: 2026-01-20T18:37:05.670Z
Learning: In the tywrap repo, ESLint is used for linting (not Biome). Do not flag regex literals that contain control characters (e.g., \u001b, \u0000-\u001F) as lint errors, since the current ESLint configuration allows them. When reviewing TypeScript files (e.g., src/**/*.ts), rely on ESLint results and avoid raising issues about these specific control-character regex literals unless there is a competing config change or a policy requiring explicit escaping.

Applied to files:

  • src/runtime/process-io.ts
  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-20T16:00:49.829Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:203-211
Timestamp: 2026-01-20T16:00:49.829Z
Learning: In the BridgeProtocol implementation (tywrap), reject Map and Set explicitly before serialization (e.g., in safeStringify or the serialization entrypoint) with a clear error like "Bridge protocol does not support Map/Set values". Do not rely on post-hoc checks of non-string keys at the point of JSON.stringify, since Maps/Sets cannot round-trip. This should be enforced at the boundary where data is prepared for serialization to ensure deterministic errors and prevent invalid data from propagating.

Applied to files:

  • src/runtime/process-io.ts
  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
  • src/runtime/safe-codec.ts
📚 Learning: 2026-01-19T21:14:40.872Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-263
Timestamp: 2026-01-19T21:14:40.872Z
Learning: In `src/runtime/bridge-core.ts` and similar hot request/response loop implementations in the tywrap repository, avoid adding extra defensive validation (e.g., runtime shape checks on error payloads) in tight loops unless the protocol boundary is untrusted or there's a concrete bug report. The Python bridge protocol is controlled and validated via tests, so defensive checks would add unnecessary branching overhead without meaningful benefit.

Applied to files:

  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
  • runtime/python_bridge.py
📚 Learning: 2026-01-19T21:14:37.032Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:375-385
Timestamp: 2026-01-19T21:14:37.032Z
Learning: In tywrap (src/runtime/bridge-core.ts and similar), environment variable parsing follows a tolerant/best-effort policy. For example, `TYWRAP_CODEC_MAX_BYTES=1024abc` should be accepted as 1024. Only reject clearly invalid values (non-numeric start or <=0). This avoids surprising failures from minor typos.

Applied to files:

  • test/safe-codec.test.ts
📚 Learning: 2026-01-19T21:00:30.825Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/adversarial_playground.test.ts:339-360
Timestamp: 2026-01-19T21:00:30.825Z
Learning: In adversarial tests for the bridge (test/adversarial_playground.test.ts), recovery assertions should be kept in the finally block adjacent to cleanup to verify resilience under stress, even if that means post-timeout checks might mask the original error. The goal is to surface recovery failures as legitimate test failures.

Applied to files:

  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-19T21:48:27.823Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: test/runtime_bridge_fixtures.test.ts:59-66
Timestamp: 2026-01-19T21:48:27.823Z
Learning: In fixture-based tests (e.g., test/runtime_bridge_fixtures.test.ts) and similar tests in the tywrap repository, prefer early returns when Python or fixture files are unavailable. Do not rely on Vitest dynamic skip APIs; a silent pass is intentional for environments lacking Python/fixtures. Treat missing fixtures as optional soft-guards and ensure the test remains non-disruptive in non-availability scenarios.

Applied to files:

  • test/safe-codec.test.ts
  • test/runtime_node.test.ts
📚 Learning: 2026-01-20T16:01:14.323Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 152
File: docs/adr/002-bridge-protocol.md:599-602
Timestamp: 2026-01-20T16:01:14.323Z
Learning: In `src/runtime/node-bridge.ts` (NodeBridge), a test message is sent to the Python subprocess to confirm the bridge is responsive before marking it as ready.

Applied to files:

  • test/runtime_node.test.ts
📚 Learning: 2026-01-19T21:48:45.693Z
Learnt from: bbopen
Repo: bbopen/tywrap PR: 127
File: src/runtime/bridge-core.ts:260-298
Timestamp: 2026-01-19T21:48:45.693Z
Learning: In `src/runtime/bridge-core.ts`, keep `normalizeErrorPayload` to validate error payloads from the Python subprocess. The subprocess boundary is effectively untrusted, and normalizing error responses prevents `undefined: undefined` errors on malformed payloads. Error responses are not the hot path, so the small validation overhead is acceptable for the added resilience.

Applied to files:

  • test/runtime_node.test.ts
  • src/runtime/safe-codec.ts
🧬 Code graph analysis (2)
test/runtime_node.test.ts (1)
scripts/act.mjs (1)
  • output (59-59)
src/runtime/safe-codec.ts (1)
src/runtime/errors.ts (1)
  • BridgeCodecError (17-29)
🪛 Ruff (0.15.0)
runtime/python_bridge.py

[warning] 147-147: Missing return type annotation for private function _deserialize_bytes_envelope

(ANN202)


[warning] 164-164: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 168-168: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 173-173: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 177-177: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (7)
src/runtime/process-io.ts (1)

736-739: LGTM — safe guard for cross-runtime compatibility.

The typeof check before calling unref() is the correct defensive pattern for runtimes that don't expose Node-style timer handles. This is outside the hot request/response loop, so the extra branch has no meaningful overhead.

runtime/python_bridge.py (2)

144-198: Well-structured sentinel + recursive deserialization pattern.

The _NO_DESERIALIZE sentinel cleanly distinguishes "not a bytes envelope" from a decoded bytes value, and the recursive deserialize correctly walks only JSON-parsed structures (no cycle risk). Consistent application across all three handler boundaries (lines 707–708, 718–719, 731–732) is good.


707-708: Deserialization correctly applied at all three request handler boundaries.

handle_call, handle_instantiate, and handle_call_method all use deserialize(coerce_list(...)) / deserialize(coerce_dict(...)) consistently, ensuring byte envelopes in args and kwargs are decoded before invocation.

Also applies to: 718-719, 731-732

src/runtime/safe-codec.ts (2)

330-350: LGTM — fromBase64 correctly handles both Node and browser runtimes.

The Buffer.from path correctly extracts byteOffset and length from the Node Buffer, and the atob path uses Array.from with a mapping function (no spread-operator stack limit concern). Empty base64 strings are handled correctly on the JS side.


359-374: LGTM — reviver handles both envelope shapes cleanly.

The bottom-up evaluation order of JSON.parse revivers means nested bytes envelopes are correctly decoded before their parent objects are processed. The type checks are minimal and appropriate for the controlled protocol.

test/safe-codec.test.ts (1)

360-368: Good regression test for the legacy bytes envelope path.

The test correctly validates that the __type__: 'bytes' envelope with base64 data decodes to a Uint8Array with the expected byte values through decodeResponse. A companion test for the __tywrap_bytes__ envelope shape would complete coverage of both reviver branches, as noted in a prior review.

test/runtime_node.test.ts (1)

115-131: Solid end-to-end regression test for the bytes round-trip.

This test exercises both directions of the fix: JS Uint8Array → Python bytes (via builtins.len) and Python bytes → JS Uint8Array (via builtins.bytes). The Python-availability guard follows the established repo pattern.

@bbopen bbopen merged commit 6245e95 into main Feb 16, 2026
18 checks passed
@bbopen bbopen deleted the codex/tywrap-open-issue-reliability-pass branch February 16, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:codec Area: codecs and serialization area:runtime-node Area: Node runtime bridge bug Something isn't working priority:p2 Priority P2 (medium)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

runtime-node: restore bytes semantics across JS↔Python bridge

1 participant