Skip to content

Codex-generated pull request#187

Merged
bbopen merged 1 commit intomainfrom
codex/implement-tywrap-architectural-remediation-plan
Feb 13, 2026
Merged

Codex-generated pull request#187
bbopen merged 1 commit intomainfrom
codex/implement-tywrap-architectural-remediation-plan

Conversation

@bbopen
Copy link
Owner

@bbopen bbopen commented Feb 12, 2026

Codex generated this pull request, but encountered an unexpected error after generation. This is a placeholder PR message.


Codex Task

@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces timeout support across the module discovery and Python IR fetching pipeline by adding optional timeoutMs parameters to process execution calls, and improves TypeScript config loading robustness by using isolated temporary directories with proper cleanup and module resolution rewriting.

Changes

Cohort / File(s) Summary
Config Loading Improvements
src/config/index.ts
Introduces temporary directory handling for TS config transpilation using OS temp space, rewrites import paths for ESM configs using import.meta.resolve, optionally symlinks local node_modules, and ensures complete cleanup of temp files and directories.
Timeout Propagation Infrastructure
src/core/discovery.ts, src/tywrap.ts, src/utils/runtime.ts
Adds optional timeoutMs parameter to DiscoveryOptions, propagates it through Python subprocess invocations via processUtils.exec, and implements timeout mechanism in runtime with kill signal and error handling for exceeded timeouts.
Test Coverage
test/config_loading.test.ts, test/discovery.test.ts
Adds test for read-only directory config loading with permission restoration, and test verifying timeoutMs propagation to subprocess calls.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, area:tooling

Poem

🐰 Timeouts tick and temp dirs bloom,
Config loads in a safer room,\
No more chaos, cleanup's clean,\
Module paths are now between,\
A rabbit's work is never through! ⏱️

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is completely generic and off-topic, providing only placeholder text and a reference to a Codex task without describing any actual changes made in the pull request. Provide a meaningful description of the changes, including the rationale for subprocess timeouts, config file isolation, and module resolution improvements.
Title check ❓ Inconclusive The title is vague and generic, using 'Codex-generated' which does not convey meaningful information about the actual changes (timeout handling, temp config isolation, or module resolution improvements). Replace with a more descriptive title that captures the main changes, such as 'Add subprocess timeouts and temp config isolation' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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/implement-tywrap-architectural-remediation-plan

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:tooling Area: tooling and CLI enhancement New feature or request labels Feb 12, 2026
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: e57a4b6c63

ℹ️ 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 +330 to +331
const tempDir = await mkdtemp(resolve(tmpdir(), 'tywrap-config-'));
const tmpPath = resolve(tempDir, `.tywrap.config.${randomUUID()}.mjs`);

Choose a reason for hiding this comment

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

P1 Badge Keep TS config temp module near source for relative imports

In loadConfigFile, emitting the transpiled .mjs into an OS temp directory changes the module resolution base, so configs that use relative imports (for example import './helper') now fail with Cannot find module because Node resolves from /tmp/... instead of the config’s directory. This is a regression from the previous behavior and breaks valid multi-file TS config setups.

Useful? React with 👍 / 👎.

Comment on lines +283 to +284
.replaceAll("'tywrap'", `'${tywrapEntryHref}'`)
.replaceAll('\"tywrap\"', `\"${tywrapEntryHref}\"`);

Choose a reason for hiding this comment

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

P2 Badge Restrict tywrap rewrite to actual import specifiers

The replaceAll calls rewrite every 'tywrap'/"tywrap" string in transpiled output, not just import specifiers, so ordinary config values are mutated (for example a literal 'tywrap' value becomes a file://... URL). This silently alters user configuration and should be constrained to import/export source strings only.

Useful? React with 👍 / 👎.

coderabbitai[bot]
coderabbitai bot previously requested changes Feb 12, 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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/utils/runtime.ts (1)

444-453: ⚠️ Potential issue | 🟡 Minor

Deno and Bun runtimes do not receive timeoutMs or env/cwd support.

The options parameter is accepted at the function signature level, but the Deno and Bun branches ignore timeoutMs, env, and cwd. If these runtimes are in scope, subprocess calls from discovery/tywrap will run without timeout enforcement there.

Also applies to: 455-473

🤖 Fix all issues with AI agents
In `@src/config/index.ts`:
- Around line 330-352: The finally block currently calls rm(tmpPath, ...) then
rm(tempDir, { recursive: true, force: true }); remove the redundant explicit
removal of tmpPath (the line invoking rm(tmpPath, ...)) since removing tempDir
recursively already deletes tmpPath; keep the recursive rm(tempDir, { recursive:
true, force: true }) and leave mkdtemp, tmpPath, transpiledOutput, writeFile,
import(pathToFileURL(...)), and ensureConfigObject untouched.
- Around line 276-288: The current naive replaceAll on transpiledOutput risks
replacing non-module string literals; update the logic that runs when
emitCommonJs is false (the block using transpiledOutput and
import.meta.resolve('tywrap')) to only rewrite module specifiers in
import/export/from and dynamic import/require calls by matching targeted
patterns (e.g. "from 'tywrap'", "from \"tywrap\"", "import('tywrap')",
"require('tywrap')", and export-from forms) using regex-based replacements
rather than blind replaceAll, so only module specifiers are swapped to the
tywrapEntryHref while leaving other string literals (e.g. name: 'tywrap')
unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02db8ff and e57a4b6.

📒 Files selected for processing (6)
  • src/config/index.ts
  • src/core/discovery.ts
  • src/tywrap.ts
  • src/utils/runtime.ts
  • test/config_loading.test.ts
  • test/discovery.test.ts
🧰 Additional context used
🧠 Learnings (6)
📚 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/discovery.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/discovery.test.ts
  • test/config_loading.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/discovery.test.ts
  • src/core/discovery.ts
  • src/utils/runtime.ts
  • test/config_loading.test.ts
  • src/config/index.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/discovery.test.ts
  • src/core/discovery.ts
  • src/utils/runtime.ts
  • test/config_loading.test.ts
  • src/config/index.ts
  • src/tywrap.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/discovery.test.ts
  • src/core/discovery.ts
  • src/utils/runtime.ts
  • test/config_loading.test.ts
  • src/config/index.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:

  • src/tywrap.ts
🧬 Code graph analysis (4)
src/core/discovery.ts (1)
src/utils/runtime.ts (1)
  • processUtils (422-549)
test/config_loading.test.ts (1)
src/config/index.ts (1)
  • loadConfigFile (228-356)
src/config/index.ts (2)
scripts/act.mjs (1)
  • output (59-59)
src/utils/runtime.ts (2)
  • resolve (302-320)
  • writeFile (386-416)
src/tywrap.ts (1)
src/utils/runtime.ts (1)
  • processUtils (422-549)
🔇 Additional comments (7)
test/discovery.test.ts (1)

171-187: LGTM!

The test correctly verifies that timeoutMs is propagated through to processUtils.exec when ModuleDiscovery is constructed with a timeout. Mock setup and teardown are clean, and the assertion properly uses toHaveBeenCalledWith with matchers for the non-timeout arguments.

test/config_loading.test.ts (1)

36-51: Good test for read-only directory handling; note platform limitation.

This test validates that TS config loading works when the source directory is read-only, confirming the new OS-temp-dir approach. The try/finally cleanup pattern correctly restores permissions before removal.

Note: chmod(dir, 0o555) has no effect on Windows, so this test only exercises the read-only scenario on Unix-like systems. If Windows CI coverage matters, consider gating with a platform check or using a Windows-specific approach.

src/tywrap.ts (1)

254-267: LGTM!

The timeoutMs parameter is cleanly threaded through fetchPythonIr to both the primary and fallback processUtils.exec calls. The optional parameter correctly defaults to undefined, which the runtime layer handles by skipping timeout setup.

src/core/discovery.ts (2)

29-29: LGTM — consistent timeoutMs propagation across all subprocess calls.

The new timeoutMs option is correctly forwarded to resolvePythonPath, getPythonSearchPaths, and getModuleVersion exec invocations.


46-52: No action needed. The resolvePythonExecutable function does not internally spawn subprocesses—it performs only path manipulation to resolve the Python executable location. The function definition in src/utils/python.ts contains no subprocess calls and accepts only pythonPath, virtualEnv, and cwd options. Since no subprocess spawning occurs, there is no timeout concern to address.

Likely an incorrect or invalid review comment.

src/utils/runtime.ts (2)

435-436: LGTM — clean environment construction and options forwarding.

The env clone via Object.create(null) avoids prototype pollution, undefined values are correctly filtered, and the options.env overrides including deletions are well-handled. PYTHONPATH prepending is preserved.

Also applies to: 481-501


504-543: Timeout mechanism is correctly implemented with proper settlement guards.

The settled flag + finish helper pattern ensures single resolution/rejection across all event paths (close, error, timeout), and the timer is cleared in every settlement path — consistent with best practices for avoiding timer leaks. Based on learnings, ensuring clearTimeout is invoked in all paths and using a guard to cancel pending timers when the promise settles is the expected pattern.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +276 to +288
let transpiledOutput = output.outputText;
if (!emitCommonJs) {
try {
// Preserve support for `import { defineConfig } from 'tywrap'` when ESM config
// is evaluated from an OS temp directory outside the package scope.
const tywrapEntryHref = await import.meta.resolve('tywrap');
transpiledOutput = transpiledOutput
.replaceAll("'tywrap'", `'${tywrapEntryHref}'`)
.replaceAll('\"tywrap\"', `\"${tywrapEntryHref}\"`);
} catch {
// Best-effort: leave source as-is when tywrap cannot be resolved.
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Naive string replacement may over-match non-import occurrences of 'tywrap'.

replaceAll("'tywrap'", ...) replaces every occurrence of the literal string 'tywrap' in the transpiled output, not just import/from specifiers. If a user config contains a string value like name: 'tywrap', it will be rewritten to a file:// URL. For a typical config file this is unlikely to cause issues in practice, but a targeted regex (e.g. matching from 'tywrap' or import('tywrap')) would be safer.

♻️ More targeted replacement
-        transpiledOutput = transpiledOutput
-          .replaceAll("'tywrap'", `'${tywrapEntryHref}'`)
-          .replaceAll('\"tywrap\"', `\"${tywrapEntryHref}\"`);
+        transpiledOutput = transpiledOutput
+          .replace(/from\s+['"]tywrap['"]/g, `from '${tywrapEntryHref}'`)
+          .replace(/import\s*\(\s*['"]tywrap['"]\s*\)/g, `import('${tywrapEntryHref}')`);
🤖 Prompt for AI Agents
In `@src/config/index.ts` around lines 276 - 288, The current naive replaceAll on
transpiledOutput risks replacing non-module string literals; update the logic
that runs when emitCommonJs is false (the block using transpiledOutput and
import.meta.resolve('tywrap')) to only rewrite module specifiers in
import/export/from and dynamic import/require calls by matching targeted
patterns (e.g. "from 'tywrap'", "from \"tywrap\"", "import('tywrap')",
"require('tywrap')", and export-from forms) using regex-based replacements
rather than blind replaceAll, so only module specifiers are swapped to the
tywrapEntryHref while leaving other string literals (e.g. name: 'tywrap')
unchanged.

Comment on lines +330 to 352
const tempDir = await mkdtemp(resolve(tmpdir(), 'tywrap-config-'));
const tmpPath = resolve(tempDir, `.tywrap.config.${randomUUID()}.mjs`);
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from config location
await writeFile(tmpPath, output.outputText, 'utf-8');
const localNodeModules = resolve(process.cwd(), 'node_modules');
if (safeExists(localNodeModules)) {
const tempNodeModules = resolve(tempDir, 'node_modules');
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await symlink(localNodeModules, tempNodeModules, 'dir');
} catch {
// Best-effort only; regular resolution may still succeed without a symlink.
}
}

// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await writeFile(tmpPath, transpiledOutput, 'utf-8');
const mod = (await import(pathToFileURL(tmpPath).href)) as Record<string, unknown>;
const loaded = mod.default ?? mod;
return ensureConfigObject(loaded ?? {}, resolved);
} finally {
await rm(tmpPath, { force: true });
await rm(tempDir, { recursive: true, force: true });
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Temp directory handling and cleanup look solid; minor redundancy in cleanup.

The approach of creating an isolated temp directory with a symlinked node_modules for ESM evaluation is well-designed. The finally block ensures cleanup in all paths.

Line 350 (rm(tmpPath, ...)) is redundant since line 351 already removes tempDir recursively (which contains tmpPath). Not a bug, but the explicit file removal can be dropped.

♻️ Remove redundant cleanup step
     } finally {
-      await rm(tmpPath, { force: true });
       await rm(tempDir, { recursive: true, force: true });
     }
📝 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
const tempDir = await mkdtemp(resolve(tmpdir(), 'tywrap-config-'));
const tmpPath = resolve(tempDir, `.tywrap.config.${randomUUID()}.mjs`);
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from config location
await writeFile(tmpPath, output.outputText, 'utf-8');
const localNodeModules = resolve(process.cwd(), 'node_modules');
if (safeExists(localNodeModules)) {
const tempNodeModules = resolve(tempDir, 'node_modules');
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await symlink(localNodeModules, tempNodeModules, 'dir');
} catch {
// Best-effort only; regular resolution may still succeed without a symlink.
}
}
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await writeFile(tmpPath, transpiledOutput, 'utf-8');
const mod = (await import(pathToFileURL(tmpPath).href)) as Record<string, unknown>;
const loaded = mod.default ?? mod;
return ensureConfigObject(loaded ?? {}, resolved);
} finally {
await rm(tmpPath, { force: true });
await rm(tempDir, { recursive: true, force: true });
}
const tempDir = await mkdtemp(resolve(tmpdir(), 'tywrap-config-'));
const tmpPath = resolve(tempDir, `.tywrap.config.${randomUUID()}.mjs`);
try {
const localNodeModules = resolve(process.cwd(), 'node_modules');
if (safeExists(localNodeModules)) {
const tempNodeModules = resolve(tempDir, 'node_modules');
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await symlink(localNodeModules, tempNodeModules, 'dir');
} catch {
// Best-effort only; regular resolution may still succeed without a symlink.
}
}
// eslint-disable-next-line security/detect-non-literal-fs-filename -- temp path is derived from OS temp directory
await writeFile(tmpPath, transpiledOutput, 'utf-8');
const mod = (await import(pathToFileURL(tmpPath).href)) as Record<string, unknown>;
const loaded = mod.default ?? mod;
return ensureConfigObject(loaded ?? {}, resolved);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
🤖 Prompt for AI Agents
In `@src/config/index.ts` around lines 330 - 352, The finally block currently
calls rm(tmpPath, ...) then rm(tempDir, { recursive: true, force: true });
remove the redundant explicit removal of tmpPath (the line invoking rm(tmpPath,
...)) since removing tempDir recursively already deletes tmpPath; keep the
recursive rm(tempDir, { recursive: true, force: true }) and leave mkdtemp,
tmpPath, transpiledOutput, writeFile, import(pathToFileURL(...)), and
ensureConfigObject untouched.

Copy link
Owner Author

@bbopen bbopen left a comment

Choose a reason for hiding this comment

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

need to change repo config for PRs

@bbopen bbopen merged commit f1bf164 into main Feb 13, 2026
20 checks passed
@bbopen bbopen deleted the codex/implement-tywrap-architectural-remediation-plan branch February 13, 2026 04:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tooling Area: tooling and CLI codex enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant