Repository navigation
Introduce first-class Codex runtime integration - #2546
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughGenie adds packaged Codex support alongside Claude, including runtime plugin installation, lifecycle hooks, provider-aware Omni execution, persisted Codex sessions, MCP fallback handling, updated launch behavior, diagnostics, and native delegation guidance. ChangesCodex runtime integration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces first-class Codex integration alongside the existing Claude Code support. It establishes a canonical, runtime-neutral contributor contract in AGENTS.md, updates the CLI commands (install, setup, uninstall, init, launch, doctor) to manage Codex plugins, role agents, and MCP configurations, and transitions shared skills to use runtime-neutral delegation terms. Additionally, it removes the obsolete loopback OTel exporter from Codex configurations, adds provider-neutral execution and thread persistence in the Omni runner, and adapts lifecycle hooks for Codex. The review feedback highlights a few important issues: a potential malformed TOML generation in runtime-integrations.ts due to unhandled indentation in regex replacement, a potential crash in genie doctor's MCP probe if non-JSON output is encountered, and the blocking of the single-threaded event loop in the Omni runner daemon due to synchronous file I/O in runCodexSession.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const replacement = /(^|\n)enabled\s*=\s*(true|false)/.test(section) | ||
| ? section.replace(/(^|\n)enabled\s*=\s*(true|false)/, `$1enabled = ${enabled}`) | ||
| : section.replace(header, `${header}\nenabled = ${enabled}`); |
There was a problem hiding this comment.
The regular expression /(^|\n)enabled\s*=\s*(true|false)/ does not account for any leading whitespace (such as spaces or tabs used for indentation) before the enabled key. If the user's config.toml has indented keys under the [plugins."genie@automagik"] section (e.g., enabled = true), the test will fail, and a duplicate enabled = false key will be appended directly under the header, resulting in a malformed TOML file that Codex will fail to parse.
To fix this, update the regex and replacement to match and preserve optional leading indentation.
| const replacement = /(^|\n)enabled\s*=\s*(true|false)/.test(section) | |
| ? section.replace(/(^|\n)enabled\s*=\s*(true|false)/, `$1enabled = ${enabled}`) | |
| : section.replace(header, `${header}\nenabled = ${enabled}`); | |
| const replacement = /(^|\n)[ \t]*enabled\s*=\s*(true|false)/.test(section) | |
| ? section.replace(/(^|\n)([ \t]*)enabled\s*=\s*(true|false)/, "$1$2enabled = " + enabled) | |
| : section.replace(header, header + "\nenabled = " + enabled); |
| const replies = stdout | ||
| .trim() | ||
| .split('\n') | ||
| .map((line) => JSON.parse(line) as { id?: number; result?: unknown; error?: unknown }); |
There was a problem hiding this comment.
If the genie binary prints any non-JSON output (such as startup logs, deprecation warnings, or update notifications) to stdout, JSON.parse(line) will throw a SyntaxError, causing probeGenieMcp to fail and report a false-positive warning in genie doctor.
To make this check robust against process noise, wrap JSON.parse in a try-catch block and filter out any non-JSON lines, similar to how extractCodexJsonlReply handles JSONL streams.
const replies = stdout
.trim()
.split("\n")
.map((line) => {
try {
return JSON.parse(line) as { id?: number; result?: unknown; error?: unknown };
} catch {
return null;
}
})
.filter((reply): reply is NonNullable<typeof reply> => reply !== null);| opts: AgentExecutionOpts, | ||
| rawSpawn: RawCodexSpawn, | ||
| ): Promise<AgentExecutionResult> { | ||
| const persona = opts.personaFile && existsSync(opts.personaFile) ? readFileSync(opts.personaFile, 'utf8') : ''; |
There was a problem hiding this comment.
Using synchronous file I/O (readFileSync) inside the asynchronous runCodexSession function blocks the single-threaded event loop of the Omni runner. Since the Omni runner is a resident daemon process handling incoming WhatsApp messages and approvals, blocking the event loop can introduce latency or cause timeouts for concurrent requests.
Consider using asynchronous file reading instead, such as Bun.file(path).text() (since the project runs on Bun) or fs.promises.readFile.
| const persona = opts.personaFile && existsSync(opts.personaFile) ? readFileSync(opts.personaFile, 'utf8') : ''; | |
| const persona = opts.personaFile && existsSync(opts.personaFile) ? await Bun.file(opts.personaFile).text() : ""; |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 878b5a28ad
ℹ️ 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".
| const installed = dirname(process.execPath); | ||
| if (existsSync(join(installed, 'plugins', 'genie'))) return installed; | ||
| return resolve(import.meta.dir, '..', '..'); |
There was a problem hiding this comment.
Resolve integrations from the normalized bundle root
On a fresh tarball install, install.sh extracts payloads under ~/.genie/bin and installCommand runs normalizeAuxLayout() before installRuntimeIntegrations(), which moves bin/plugins to ~/.genie/plugins. After that move this resolver only checks ~/.genie/bin/plugins/genie and then falls back to the source-tree-relative path, so Codex integration cannot find plugins/genie/codex-agents; explicit --integrations codex fails and auto install silently skips the new integration. Check the normalized ~/.genie root, or pass it as the bundle root, before falling back.
Useful? React with 👍 / 👎.
| "repository": "https://github.com/automagik-dev/genie", | ||
| "license": "MIT", | ||
| "keywords": ["workflow", "orchestration", "codex", "skills", "agents"], | ||
| "skills": "./skills/", |
There was a problem hiding this comment.
Bundle the skills directory inside the Codex plugin
The Codex manifest declares skills as ./skills/, and Codex plugin paths are resolved relative to the plugin root (OpenAI docs). The release payload and repo currently keep the shared skills as a sibling top-level skills/ tree, while plugins/genie/skills does not exist, so an installed Genie Codex plugin will not expose $wish, $work, $review, etc. Include/sync the shared skills under the plugin root (or point the manifest at a real bundled plugin-root path).
Useful? React with 👍 / 👎.
| let count = 0; | ||
| for (const name of readdirSync(source)) { | ||
| if (!name.startsWith('genie-') || !name.endsWith('.toml')) continue; | ||
| copyFileSync(join(source, name), join(target, name)); |
There was a problem hiding this comment.
Preserve existing unmanaged Codex agent files
When genie setup --codex runs on a host that already has a user-created ~/.codex/agents/genie-*.toml, this unconditional copy overwrites that file with a Genie-managed marker; genie uninstall can then remove it because it now looks marker-owned. Please mirror the uninstall/agent-sync ownership policy here by only replacing absent or already marker-owned files, or by backing up unmanaged collisions first.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.genie/agents/metrics-updater/runs.jsonl:
- Line 6: Align the new metrics record with the established schema: change
errors back to an array and restore tools_used as the string-array field,
removing or renaming tools_generated as appropriate. Update the
record-generation logic associated with the metrics updater so all emitted
records consistently use these existing field types and names.
In `@skills/docs/SKILL.md`:
- Line 36: In the dispatch instruction, correct the article before “native
delegation surface call” from “an” to “a,” preserving the rest of the sentence.
In `@skills/genie/SKILL.md`:
- Line 53: The shared v5 runtime guidance in the referenced section hardcodes
client names, violating runtime-neutral skill conventions. Update the wording
around the v5 execution model and runtime mapping to use generic terms such as
“native subagents” and “native runtime,” while retaining client-specific
mappings only in native-surfaces.md.
In `@src/genie-commands/doctor.ts`:
- Line 244: Replace the hardcoded expected count in the doctor status check with
a dynamic value, preferably by counting TOML files in the staged
plugins/genie/codex-agents/ directory or reusing a shared agent-count constant.
Update the logic around the status calculation to compare count against that
derived value instead of 7.
- Around line 237-248: codexAgentCheck should handle filesystem errors from
readdirSync instead of allowing doctorCommand to crash. Wrap the agents
directory existence check and directory read in a try-catch, returning a warn
CheckResult with an appropriate failure detail and repair suggestion when an
exception occurs; preserve the current count-based result on success.
In `@src/genie-commands/install.test.ts`:
- Around line 75-98: The test for --skip-integrations makes an unverified first
installCommand call whose selection is overwritten. Add an assertion immediately
after the first call confirming selection is 'auto', then retain the second call
and final assertion confirming 'none', or remove the first call if the default
behavior is not intended to be tested.
- Around line 100-119: Add a test in the install command test suite covering an
unsupported integrations selection, casting the invalid string to
IntegrationSelection. Invoke installCommand with that value and assert it throws
with the exact “Invalid --integrations value” message, including the command’s
expected error behavior.
In `@src/genie-commands/setup.ts`:
- Around line 189-199: repairCodexIntegration currently removes the fallback
relative to process.cwd(), which can differ from the git root used by init.ts.
Resolve the repository root with resolveGitRoot(process.cwd()) and pass
join(root, '.codex', 'config.toml') to removeCodexMcpFallback, handling the
resolved root consistently with the existing integration setup.
In `@src/genie-commands/uninstall.ts`:
- Around line 233-234: Wrap the removeRuntimeIntegrations call in
performUninstall with tryRemoveStep, matching the other uninstall removal steps
so it reports progress and isolates errors while preserving the existing
removeMarketplace argument.
In `@src/hooks/codex-adapter.test.ts`:
- Around line 8-36: Add tests in the “Codex hook adapter” suite covering
non-JSON input to adaptCodexPreToolUseOutput (preserving it unchanged), allow
and deny PreToolUse outputs (also unchanged), undefined input to
codexPermissionDecision (returning an empty string), and
dispatchCodexPermissionRequest approvals producing allow and deny results with
formatted Codex PermissionRequest JSON rather than an empty string.
In `@src/lib/codex-config.test.ts`:
- Around line 1-34: Add an error-path test to the migrateDeadGenieOtel test
suite that passes a directory path as configPath, triggering readFileSync
failure and the catch branch. Assert the returned result has status 'error' and
verify the error response fields as appropriate, while ensuring the temporary
directory is cleaned up.
In `@src/lib/runtime-integrations.test.ts`:
- Around line 1-68: Add afterEach-based cleanup for all temporary directories
created in the test suite. Define a temp-directory collection and remove each
registered path recursively after tests, then update the mkdtempSync calls in
“restores an explicit Codex disabled state without touching other plugins” and
“fresh Codex home installs agents, migrates dead OTel, and runs idempotent
marketplace/plugin commands” to register their returned paths.
In `@src/lib/runtime-integrations.ts`:
- Around line 175-195: Make removeRuntimeIntegrations testable by adding an
injectable CommandRunner parameter, matching installRuntimeIntegrations. Default
it to defaultRunner, then replace every direct defaultRunner call for plugin and
marketplace removal with the injected runner while preserving existing behavior
and arguments.
- Around line 49-55: Update codexPluginState so enabled is undefined when
plugin.enabled is absent, and only map it to a boolean when the value is
actually boolean; retain version handling and installed detection. Apply the
same enabled mapping in claudePluginState so both runtime state parsers preserve
missing enabled values and prevent refresh logic from writing enabled = false.
In `@src/term-commands/init.test.ts`:
- Around line 240-263: The tests cover direct fallback behavior but not the
end-to-end Codex registration path. Add an integration test around
registerMcpConfigs that mocks Bun.which('codex') as available and
codexHasGeniePlugin() as absent, then assert the init result includes the Codex
fallback entry.
In `@src/term-commands/init.ts`:
- Around line 244-260: Update mergeCodexMcpFallback to serialize entry.args
instead of the hardcoded ["mcp"], preserving the expected TOML array format.
Handle an orphaned begin marker by replacing or repairing the existing
incomplete fallback block rather than appending another block; ensure the
resulting configuration contains exactly one complete BEGIN/END marker pair.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bc781f73-143e-4a96-bee7-d0b2cb5797e5
⛔ Files ignored due to path filters (7)
AGENTS.mdis excluded by!*.mdCLAUDE.mdis excluded by!*.mdREADME.mdis excluded by!*.mdplugins/genie/scripts/session-context.cjsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/src/session-context.tsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/src/validate-completion.tsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/validate-completion.cjsis excluded by!plugins/genie/scripts/**
📒 Files selected for processing (63)
.agents/plugins/marketplace.json.claude-plugin/marketplace.json.genie/agents/metrics-updater/runs.jsonl.genie/agents/metrics-updater/state.json.github/workflows/build-tarballs.ymlinstall.shpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/.codex-plugin/plugin.jsonplugins/genie/codex-agents/genie-engineer-complex.tomlplugins/genie/codex-agents/genie-engineer-standard.tomlplugins/genie/codex-agents/genie-engineer-trivial.tomlplugins/genie/codex-agents/genie-final-gate.tomlplugins/genie/codex-agents/genie-fixer.tomlplugins/genie/codex-agents/genie-reviewer.tomlplugins/genie/codex-agents/genie-scout.tomlplugins/genie/hooks/claude-hooks.jsonplugins/genie/hooks/hooks.jsonplugins/genie/package.jsonplugins/genie/references/native-surfaces.mdscripts/build-binary.shscripts/build.jsskills/brainstorm/SKILL.mdskills/council/SKILL.mdskills/docs/SKILL.mdskills/dream/SKILL.mdskills/fix/SKILL.mdskills/genie/SKILL.mdskills/pm/SKILL.mdskills/report/SKILL.mdskills/review/SKILL.mdskills/trace/SKILL.mdskills/wish/SKILL.mdskills/wizard/SKILL.mdskills/work/SKILL.mdsrc/__tests__/claude-md-drift.test.tssrc/genie-commands/doctor.tssrc/genie-commands/install.test.tssrc/genie-commands/install.tssrc/genie-commands/setup.tssrc/genie-commands/uninstall.tssrc/genie.tssrc/hooks/codex-adapter.test.tssrc/hooks/codex-adapter.tssrc/hooks/dispatch-command.tssrc/hooks/index.tssrc/lib/codex-config.test.tssrc/lib/codex-config.tssrc/lib/genie-config.tssrc/lib/omni-config.tssrc/lib/omni-registration.tssrc/lib/omni-runner.test.tssrc/lib/omni-runner.tssrc/lib/runtime-integrations.test.tssrc/lib/runtime-integrations.tssrc/lib/v5/global-db.test.tssrc/lib/v5/global-db.tssrc/term-commands/init.test.tssrc/term-commands/init.tssrc/term-commands/launch.test.tssrc/term-commands/launch.tssrc/term-commands/omni.test.tssrc/types/genie-config.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 16
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.genie/agents/metrics-updater/runs.jsonl:
- Line 6: Align the new metrics record with the established schema: change
errors back to an array and restore tools_used as the string-array field,
removing or renaming tools_generated as appropriate. Update the
record-generation logic associated with the metrics updater so all emitted
records consistently use these existing field types and names.
In `@skills/docs/SKILL.md`:
- Line 36: In the dispatch instruction, correct the article before “native
delegation surface call” from “an” to “a,” preserving the rest of the sentence.
In `@skills/genie/SKILL.md`:
- Line 53: The shared v5 runtime guidance in the referenced section hardcodes
client names, violating runtime-neutral skill conventions. Update the wording
around the v5 execution model and runtime mapping to use generic terms such as
“native subagents” and “native runtime,” while retaining client-specific
mappings only in native-surfaces.md.
In `@src/genie-commands/doctor.ts`:
- Line 244: Replace the hardcoded expected count in the doctor status check with
a dynamic value, preferably by counting TOML files in the staged
plugins/genie/codex-agents/ directory or reusing a shared agent-count constant.
Update the logic around the status calculation to compare count against that
derived value instead of 7.
- Around line 237-248: codexAgentCheck should handle filesystem errors from
readdirSync instead of allowing doctorCommand to crash. Wrap the agents
directory existence check and directory read in a try-catch, returning a warn
CheckResult with an appropriate failure detail and repair suggestion when an
exception occurs; preserve the current count-based result on success.
In `@src/genie-commands/install.test.ts`:
- Around line 75-98: The test for --skip-integrations makes an unverified first
installCommand call whose selection is overwritten. Add an assertion immediately
after the first call confirming selection is 'auto', then retain the second call
and final assertion confirming 'none', or remove the first call if the default
behavior is not intended to be tested.
- Around line 100-119: Add a test in the install command test suite covering an
unsupported integrations selection, casting the invalid string to
IntegrationSelection. Invoke installCommand with that value and assert it throws
with the exact “Invalid --integrations value” message, including the command’s
expected error behavior.
In `@src/genie-commands/setup.ts`:
- Around line 189-199: repairCodexIntegration currently removes the fallback
relative to process.cwd(), which can differ from the git root used by init.ts.
Resolve the repository root with resolveGitRoot(process.cwd()) and pass
join(root, '.codex', 'config.toml') to removeCodexMcpFallback, handling the
resolved root consistently with the existing integration setup.
In `@src/genie-commands/uninstall.ts`:
- Around line 233-234: Wrap the removeRuntimeIntegrations call in
performUninstall with tryRemoveStep, matching the other uninstall removal steps
so it reports progress and isolates errors while preserving the existing
removeMarketplace argument.
In `@src/hooks/codex-adapter.test.ts`:
- Around line 8-36: Add tests in the “Codex hook adapter” suite covering
non-JSON input to adaptCodexPreToolUseOutput (preserving it unchanged), allow
and deny PreToolUse outputs (also unchanged), undefined input to
codexPermissionDecision (returning an empty string), and
dispatchCodexPermissionRequest approvals producing allow and deny results with
formatted Codex PermissionRequest JSON rather than an empty string.
In `@src/lib/codex-config.test.ts`:
- Around line 1-34: Add an error-path test to the migrateDeadGenieOtel test
suite that passes a directory path as configPath, triggering readFileSync
failure and the catch branch. Assert the returned result has status 'error' and
verify the error response fields as appropriate, while ensuring the temporary
directory is cleaned up.
In `@src/lib/runtime-integrations.test.ts`:
- Around line 1-68: Add afterEach-based cleanup for all temporary directories
created in the test suite. Define a temp-directory collection and remove each
registered path recursively after tests, then update the mkdtempSync calls in
“restores an explicit Codex disabled state without touching other plugins” and
“fresh Codex home installs agents, migrates dead OTel, and runs idempotent
marketplace/plugin commands” to register their returned paths.
In `@src/lib/runtime-integrations.ts`:
- Around line 175-195: Make removeRuntimeIntegrations testable by adding an
injectable CommandRunner parameter, matching installRuntimeIntegrations. Default
it to defaultRunner, then replace every direct defaultRunner call for plugin and
marketplace removal with the injected runner while preserving existing behavior
and arguments.
- Around line 49-55: Update codexPluginState so enabled is undefined when
plugin.enabled is absent, and only map it to a boolean when the value is
actually boolean; retain version handling and installed detection. Apply the
same enabled mapping in claudePluginState so both runtime state parsers preserve
missing enabled values and prevent refresh logic from writing enabled = false.
In `@src/term-commands/init.test.ts`:
- Around line 240-263: The tests cover direct fallback behavior but not the
end-to-end Codex registration path. Add an integration test around
registerMcpConfigs that mocks Bun.which('codex') as available and
codexHasGeniePlugin() as absent, then assert the init result includes the Codex
fallback entry.
In `@src/term-commands/init.ts`:
- Around line 244-260: Update mergeCodexMcpFallback to serialize entry.args
instead of the hardcoded ["mcp"], preserving the expected TOML array format.
Handle an orphaned begin marker by replacing or repairing the existing
incomplete fallback block rather than appending another block; ensure the
resulting configuration contains exactly one complete BEGIN/END marker pair.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bc781f73-143e-4a96-bee7-d0b2cb5797e5
⛔ Files ignored due to path filters (7)
AGENTS.mdis excluded by!*.mdCLAUDE.mdis excluded by!*.mdREADME.mdis excluded by!*.mdplugins/genie/scripts/session-context.cjsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/src/session-context.tsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/src/validate-completion.tsis excluded by!plugins/genie/scripts/**plugins/genie/scripts/validate-completion.cjsis excluded by!plugins/genie/scripts/**
📒 Files selected for processing (63)
.agents/plugins/marketplace.json.claude-plugin/marketplace.json.genie/agents/metrics-updater/runs.jsonl.genie/agents/metrics-updater/state.json.github/workflows/build-tarballs.ymlinstall.shpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/.codex-plugin/plugin.jsonplugins/genie/codex-agents/genie-engineer-complex.tomlplugins/genie/codex-agents/genie-engineer-standard.tomlplugins/genie/codex-agents/genie-engineer-trivial.tomlplugins/genie/codex-agents/genie-final-gate.tomlplugins/genie/codex-agents/genie-fixer.tomlplugins/genie/codex-agents/genie-reviewer.tomlplugins/genie/codex-agents/genie-scout.tomlplugins/genie/hooks/claude-hooks.jsonplugins/genie/hooks/hooks.jsonplugins/genie/package.jsonplugins/genie/references/native-surfaces.mdscripts/build-binary.shscripts/build.jsskills/brainstorm/SKILL.mdskills/council/SKILL.mdskills/docs/SKILL.mdskills/dream/SKILL.mdskills/fix/SKILL.mdskills/genie/SKILL.mdskills/pm/SKILL.mdskills/report/SKILL.mdskills/review/SKILL.mdskills/trace/SKILL.mdskills/wish/SKILL.mdskills/wizard/SKILL.mdskills/work/SKILL.mdsrc/__tests__/claude-md-drift.test.tssrc/genie-commands/doctor.tssrc/genie-commands/install.test.tssrc/genie-commands/install.tssrc/genie-commands/setup.tssrc/genie-commands/uninstall.tssrc/genie.tssrc/hooks/codex-adapter.test.tssrc/hooks/codex-adapter.tssrc/hooks/dispatch-command.tssrc/hooks/index.tssrc/lib/codex-config.test.tssrc/lib/codex-config.tssrc/lib/genie-config.tssrc/lib/omni-config.tssrc/lib/omni-registration.tssrc/lib/omni-runner.test.tssrc/lib/omni-runner.tssrc/lib/runtime-integrations.test.tssrc/lib/runtime-integrations.tssrc/lib/v5/global-db.test.tssrc/lib/v5/global-db.tssrc/term-commands/init.test.tssrc/term-commands/init.tssrc/term-commands/launch.test.tssrc/term-commands/launch.tssrc/term-commands/omni.test.tssrc/types/genie-config.ts
🛑 Comments failed to post (16)
.genie/agents/metrics-updater/runs.jsonl (1)
6-6: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Schema inconsistency in new metrics record.
The new record changes
errorsfrom an array ([]) to a number (0) and replacestools_used(string array) withtools_generated(number). Any downstream parser expectingerrorsto be an array ortools_usedto exist would break on this record.If this is an intentional schema migration, ensure all consumers are updated. Otherwise, align with the existing schema:
🔧 Proposed alignment with existing schema
-{"timestamp":"2026-07-10T07:25:00Z","duration_ms":12400,"api_calls":2,"tools_generated":0,"errors":0,"metrics":{"releases_24h":9,"prs_merged_7d":39,"avg_merge_time_hours":2.9,"ship_rate_pct":23}} +{"timestamp":"2026-07-10T07:25:00Z","duration_ms":12400,"api_calls":2,"tools_used":[],"errors":[],"metrics":{"releases_24h":9,"prs_merged_7d":39,"avg_merge_time_hours":2.9,"ship_rate_pct":23}}📝 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.{"timestamp":"2026-07-10T07:25:00Z","duration_ms":12400,"api_calls":2,"tools_used":[],"errors":[],"metrics":{"releases_24h":9,"prs_merged_7d":39,"avg_merge_time_hours":2.9,"ship_rate_pct":23}}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.genie/agents/metrics-updater/runs.jsonl at line 6, Align the new metrics record with the established schema: change errors back to an array and restore tools_used as the string-array field, removing or renaming tools_generated as appropriate. Update the record-generation logic associated with the metrics updater so all emitted records consistently use these existing field types and names.skills/docs/SKILL.md (1)
36-36: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the article in the dispatch instruction.
Change “an native delegation surface call” to “a native delegation surface call” so the instruction remains clear and polished.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@skills/docs/SKILL.md` at line 36, In the dispatch instruction, correct the article before “native delegation surface call” from “an” to “a,” preserving the rest of the sentence.skills/genie/SKILL.md (1)
53-53: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Keep shared skill guidance runtime-neutral.
This shared skill now names Claude and Codex directly. Replace those names with generic “native subagents” or “native runtime” wording; keep client-specific mapping in
plugins/genie/references/native-surfaces.md.As per coding guidelines, shared skills must use roles and native delegation language, never a hardcoded client tool name.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@skills/genie/SKILL.md` at line 53, The shared v5 runtime guidance in the referenced section hardcodes client names, violating runtime-neutral skill conventions. Update the wording around the v5 execution model and runtime mapping to use generic terms such as “native subagents” and “native runtime,” while retaining client-specific mappings only in native-surfaces.md.Source: Coding guidelines
src/genie-commands/doctor.ts (2)
237-248: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
codexAgentChecklacks error handling aroundreaddirSync.
readCodexPluginStateandprobeGenieMcpboth wrap external calls in try-catch and return warn results on failure.codexAgentCheckusesexistsSyncbeforereaddirSync, but if the directory exists and is unreadable (permissions),readdirSyncthrows and crashes the entiredoctorCommand. Add a try-catch for consistency with the other Codex diagnostic helpers.🛡️ Proposed fix
function codexAgentCheck(): CheckResult { const agentsDir = join(getCodexHome(), 'agents'); - const count = existsSync(agentsDir) - ? readdirSync(agentsDir).filter((name) => /^genie-.+\.toml$/.test(name)).length - : 0; + let count = 0; + try { + count = existsSync(agentsDir) + ? readdirSync(agentsDir).filter((name) => /^genie-.+\.toml$/.test(name)).length + : 0; + } catch { + // Directory exists but is unreadable + } return { name: 'Codex Genie role agents', status: count === 7 ? 'pass' : 'warn', detail: `${count}/7 installed`, suggestion: count === 7 ? undefined : 'Run `genie setup --codex` to repair marker-owned role agents.', }; }📝 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.function codexAgentCheck(): CheckResult { const agentsDir = join(getCodexHome(), 'agents'); let count = 0; try { count = existsSync(agentsDir) ? readdirSync(agentsDir).filter((name) => /^genie-.+\.toml$/.test(name)).length : 0; } catch { // Directory exists but is unreadable } return { name: 'Codex Genie role agents', status: count === 7 ? 'pass' : 'warn', detail: `${count}/7 installed`, suggestion: count === 7 ? undefined : 'Run `genie setup --codex` to repair marker-owned role agents.', }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/doctor.ts` around lines 237 - 248, codexAgentCheck should handle filesystem errors from readdirSync instead of allowing doctorCommand to crash. Wrap the agents directory existence check and directory read in a try-catch, returning a warn CheckResult with an appropriate failure detail and repair suggestion when an exception occurs; preserve the current count-based result on success.
244-244: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Consider deriving the expected agent count dynamically instead of hardcoding
7.If a role agent is added or removed from the bundle, this magic number must be manually updated. Consider counting TOML files in the staged
plugins/genie/codex-agents/directory or extracting a shared constant.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/doctor.ts` at line 244, Replace the hardcoded expected count in the doctor status check with a dynamic value, preferably by counting TOML files in the staged plugins/genie/codex-agents/ directory or reusing a shared agent-count constant. Update the logic around the status calculation to compare count against that derived value instead of 7.src/genie-commands/install.test.ts (2)
75-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
First
installCommandcall in--skip-integrationstest is wasted.The test calls
installCommandtwice with the sameselectionvariable, but only asserts after the second call. The first call (with{}) setsselectionto'auto'but this is overwritten and never verified. Either add an assertion after the first call or remove it.🧪 Suggested fix: add assertion after first call
installCommand( {}, makeCleanupSpy().runner, () => {}, () => {}, (options) => { selection = options?.selection ?? ''; return []; }, ); + expect(selection).toBe('auto'); installCommand( { skipIntegrations: 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.test('--skip-integrations maps to none', () => { let selection = ''; installCommand( {}, makeCleanupSpy().runner, () => {}, () => {}, (options) => { selection = options?.selection ?? ''; return []; }, ); expect(selection).toBe('auto'); installCommand( { skipIntegrations: true }, makeCleanupSpy().runner, () => {}, () => {}, (options) => { selection = options?.selection ?? ''; return []; }, ); expect(selection).toBe('none'); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/install.test.ts` around lines 75 - 98, The test for --skip-integrations makes an unverified first installCommand call whose selection is overwritten. Add an assertion immediately after the first call confirming selection is 'auto', then retain the second call and final assertion confirming 'none', or remove the first call if the default behavior is not intended to be tested.Source: Learnings
100-119: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
Add test for invalid
--integrationsvalue.
install.tslines 92-93 throwInvalid --integrations valuefor unsupported selections, but no test covers this boundary. Per learnings, every new CLI surface must test command boundaries including error exit codes and error-message formats.🧪 Suggested test addition
+ test('invalid integrations value throws', () => { + expect(() => + installCommand( + { integrations: 'bogus' as unknown as IntegrationSelection }, + makeCleanupSpy().runner, + () => {}, + () => {}, + () => [], + ), + ).toThrow('Invalid --integrations value: bogus'); + });Note:
IntegrationSelectionis a string union, so the cast is needed to simulate an invalid CLI value at runtime.📝 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.test('invalid integrations value throws', () => { expect(() => installCommand( { integrations: 'bogus' as unknown as IntegrationSelection }, makeCleanupSpy().runner, () => {}, () => {}, () => [], ), ).toThrow('Invalid --integrations value: bogus'); }); test('explicit integration failures are fatal while auto failures warn', () => { const failing = () => [{ runtime: 'codex' as const, ok: false, detail: 'missing' }]; expect(() => installCommand( {}, makeCleanupSpy().runner, () => {}, () => {}, failing, ), ).not.toThrow(); expect(() => installCommand( { integrations: 'codex' }, makeCleanupSpy().runner, () => {}, () => {}, failing, ), ).toThrow('Requested integration failed');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/install.test.ts` around lines 100 - 119, Add a test in the install command test suite covering an unsupported integrations selection, casting the invalid string to IntegrationSelection. Invoke installCommand with that value and assert it throws with the exact “Invalid --integrations value” message, including the command’s expected error behavior.Source: Learnings
src/genie-commands/setup.ts (1)
189-199: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Path mismatch:
removeCodexMcpFallbacktargetsprocess.cwd()butinit.tswrites to the git root.
init.tsresolves the git root viaresolveGitRoot(process.cwd())and writes the MCP fallback tojoin(root, '.codex', 'config.toml'). ButrepairCodexIntegrationusesjoin(process.cwd(), '.codex', 'config.toml'). If the user runsgenie setup --codexfrom a subdirectory, the removal silently fails (returns'skipped'), leaving stale fallback config in the git root's.codex/config.toml.🔧 Proposed fix: resolve git root before removing fallback
function repairCodexIntegration(): boolean { const result = installRuntimeIntegrations({ selection: 'codex' })[0]; if (!result?.ok) { console.log(` \x1b[31m\u2717\x1b[0m ${result?.detail ?? 'Codex integration failed'}`); return false; } console.log(` \x1b[32m\u2713\x1b[0m ${result.detail}`); - removeCodexMcpFallback(join(process.cwd(), '.codex', 'config.toml')); + const root = resolveGitRoot(process.cwd()) ?? process.cwd(); + removeCodexMcpFallback(join(root, '.codex', 'config.toml')); if (result.preservedDisabled) console.log(' \x1b[2mExisting disabled plugin state was preserved.\x1b[0m'); console.log(' \x1b[33m!\x1b[0m Review Genie hooks with /hooks, then start a new Codex task.'); return 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.function repairCodexIntegration(): boolean { const result = installRuntimeIntegrations({ selection: 'codex' })[0]; if (!result?.ok) { console.log(` \x1b[31m\u2717\x1b[0m ${result?.detail ?? 'Codex integration failed'}`); return false; } console.log(` \x1b[32m\u2713\x1b[0m ${result.detail}`); const root = resolveGitRoot(process.cwd()) ?? process.cwd(); removeCodexMcpFallback(join(root, '.codex', 'config.toml')); if (result.preservedDisabled) console.log(' \x1b[2mExisting disabled plugin state was preserved.\x1b[0m'); console.log(' \x1b[33m!\x1b[0m Review Genie hooks with /hooks, then start a new Codex task.'); return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/setup.ts` around lines 189 - 199, repairCodexIntegration currently removes the fallback relative to process.cwd(), which can differ from the git root used by init.ts. Resolve the repository root with resolveGitRoot(process.cwd()) and pass join(root, '.codex', 'config.toml') to removeCodexMcpFallback, handling the resolved root consistently with the existing integration setup.src/genie-commands/uninstall.ts (1)
233-234: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
Wrap
removeRuntimeIntegrationsintryRemoveStepfor consistency and resilience.Every other removal step in
performUninstallusestryRemoveStepfor progress output and error isolation. CallingremoveRuntimeIntegrationsdirectly means no progress is printed and an unexpected throw aborts the uninstall before the genie directory is removed.♻️ Proposed refactor
- removeRuntimeIntegrations(removeMarketplace); + tryRemoveStep( + 'Removing runtime integrations...', + 'Runtime integrations removed', + () => removeRuntimeIntegrations(removeMarketplace), + );📝 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.tryRemoveStep( 'Removing runtime integrations...', 'Runtime integrations removed', () => removeRuntimeIntegrations(removeMarketplace), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/uninstall.ts` around lines 233 - 234, Wrap the removeRuntimeIntegrations call in performUninstall with tryRemoveStep, matching the other uninstall removal steps so it reports progress and isolates errors while preserving the existing removeMarketplace argument.src/hooks/codex-adapter.test.ts (1)
8-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add edge case tests for uncovered adapter paths.
The suite covers the happy paths but misses several important branches:
adaptCodexPreToolUseOutputwith non-JSON output (thecatchblock at line 16-18)adaptCodexPreToolUseOutputwithallow/deny(should preserve output unchanged)codexPermissionDecisionwithundefinedresult (should return'')dispatchCodexPermissionRequestwith an approval that returnsallowordeny(should return formatted Codex PermissionRequest JSON, not'')These branches are reachable in production and should be tested as part of the specification.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/codex-adapter.test.ts` around lines 8 - 36, Add tests in the “Codex hook adapter” suite covering non-JSON input to adaptCodexPreToolUseOutput (preserving it unchanged), allow and deny PreToolUse outputs (also unchanged), undefined input to codexPermissionDecision (returning an empty string), and dispatchCodexPermissionRequest approvals producing allow and deny results with formatted Codex PermissionRequest JSON rather than an empty string.Source: Coding guidelines
src/lib/codex-config.test.ts (1)
1-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add test coverage for the error path.
The
status: 'error'branch inmigrateDeadGenieOtelis untested. Passing a directory path asconfigPathwould trigger areadFileSyncerror and exercise the catch block. This is a quick addition that completes the specification.🧪 Suggested error-path test
+ test('returns error status when config cannot be read', () => { + const root = mkdtempSync(join(tmpdir(), 'genie-codex-config-')); + roots.push(root); + // Passing a directory as configPath triggers a readFileSync error + const result = migrateDeadGenieOtel(root); + expect(result.status).toBe('error'); + expect(result.error).toBeTruthy(); + });📝 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.import { afterEach, describe, expect, test } from 'bun:test'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { DEAD_GENIE_OTEL_EXPORTER, migrateDeadGenieOtel } from './codex-config.js'; const roots: string[] = []; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); describe('migrateDeadGenieOtel', () => { test('backs up and removes only the exact obsolete exporter', () => { const root = mkdtempSync(join(tmpdir(), 'genie-codex-config-')); roots.push(root); const path = join(root, 'config.toml'); const original = `disable_paste_burst = true\n[otel]\n${DEAD_GENIE_OTEL_EXPORTER}\nlog_user_prompt = true\n`; writeFileSync(path, original); const result = migrateDeadGenieOtel(path, new Date('2026-07-10T00:00:00Z')); expect(result.status).toBe('changed'); expect(readFileSync(result.backupPath!, 'utf8')).toBe(original); expect(readFileSync(path, 'utf8')).toBe('disable_paste_burst = true\n[otel]\nlog_user_prompt = true\n'); }); test('preserves unrelated exporters and writes no backup', () => { const root = mkdtempSync(join(tmpdir(), 'genie-codex-config-')); roots.push(root); const path = join(root, 'config.toml'); const content = '[otel]\nexporter = { otlp-http = { endpoint = "http://localhost:4318/v1/traces" } }\n'; writeFileSync(path, content); expect(migrateDeadGenieOtel(path).status).toBe('unchanged'); expect(readFileSync(path, 'utf8')).toBe(content); }); test('returns error status when config cannot be read', () => { const root = mkdtempSync(join(tmpdir(), 'genie-codex-config-')); roots.push(root); const result = migrateDeadGenieOtel(root); expect(result.status).toBe('error'); expect(result.error).toBeTruthy(); }); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/codex-config.test.ts` around lines 1 - 34, Add an error-path test to the migrateDeadGenieOtel test suite that passes a directory path as configPath, triggering readFileSync failure and the catch branch. Assert the returned result has status 'error' and verify the error response fields as appropriate, while ensuring the temporary directory is cleaned up.Source: Coding guidelines
src/lib/runtime-integrations.test.ts (1)
1-68: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add
afterEachcleanup for temporary directories.The test creates temp directories via
mkdtempSyncat lines 29 and 39 but never cleans them up. Coding guidelines require temporary directories to use cleanup inafterEach.🛡️ Proposed fix
import { describe, expect, test } from 'bun:test'; -import { mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { afterEach, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { @@ -10,6 +10,11 @@ } from './runtime-integrations.js'; +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }); +}); + describe('runtime plugin state', () => { test('reads Codex and Claude enabled state', () => {Then update each
mkdtempSynccall to push the result intotempDirs:- const root = mkdtempSync(join(tmpdir(), 'genie-plugin-state-')); + const root = mkdtempSync(join(tmpdir(), 'genie-plugin-state-')); + tempDirs.push(root);- const codexHome = mkdtempSync(join(tmpdir(), 'genie-codex-home-')); + const codexHome = mkdtempSync(join(tmpdir(), 'genie-codex-home-')); + tempDirs.push(codexHome);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/runtime-integrations.test.ts` around lines 1 - 68, Add afterEach-based cleanup for all temporary directories created in the test suite. Define a temp-directory collection and remove each registered path recursively after tests, then update the mkdtempSync calls in “restores an explicit Codex disabled state without touching other plugins” and “fresh Codex home installs agents, migrates dead OTel, and runs idempotent marketplace/plugin commands” to register their returned paths.Source: Coding guidelines
src/lib/runtime-integrations.ts (2)
49-55: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail # Locate the relevant file and inspect the surrounding lines. git ls-files 'src/lib/runtime-integrations.ts' wc -l src/lib/runtime-integrations.ts sed -n '1,220p' src/lib/runtime-integrations.ts # Find the downstream call sites mentioned in the comment. rg -n "codexPluginState|claudePluginState|setCodexPluginEnabled|preservedDisabled|enabled" src/lib/runtime-integrations.tsRepository: automagik-dev/genie
Length of output: 9854
Preserve missing
enabledas undefined
codexPluginStateturns an absentenabledfield intofalse, soinstallRuntimeIntegrationstreats an installed plugin as explicitly disabled and writesenabled = falseback during refresh. Keepenabledundefined unless Codex returns a boolean; apply the same mapping inclaudePluginStatefor consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/runtime-integrations.ts` around lines 49 - 55, Update codexPluginState so enabled is undefined when plugin.enabled is absent, and only map it to a boolean when the value is actually boolean; retain version handling and installed detection. Apply the same enabled mapping in claudePluginState so both runtime state parsers preserve missing enabled values and prevent refresh logic from writing enabled = false.
175-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
removeRuntimeIntegrationsis untestable — add arunnerparameter.Unlike
installRuntimeIntegrations, which accepts an injectableCommandRunner,removeRuntimeIntegrationscallsdefaultRunnerdirectly. This makes it impossible to unit-test plugin/marketplace removal with mocks, and the test file only covers installation.♻️ Proposed refactor
-export function removeRuntimeIntegrations(removeMarketplace = false): void { +export function removeRuntimeIntegrations(removeMarketplace = false, runner: CommandRunner = defaultRunner): void { const codexHome = getCodexHome(); const agentsDir = join(codexHome, 'agents'); if (existsSync(agentsDir)) { for (const name of readdirSync(agentsDir)) { if (!/^genie-.+\.toml$/.test(name)) continue; const path = join(agentsDir, name); try { if (readFileSync(path, 'utf8').startsWith('# Managed by Genie.')) unlinkSync(path); } catch { // Preserve unreadable or user-modified files. } } } - if (commandExists('codex')) defaultRunner('codex', ['plugin', 'remove', 'genie@automagik']); - if (commandExists('claude')) defaultRunner('claude', ['plugin', 'uninstall', 'genie@automagik']); + if (commandExists('codex')) runner('codex', ['plugin', 'remove', 'genie@automagik']); + if (commandExists('claude')) runner('claude', ['plugin', 'uninstall', 'genie@automagik']); if (removeMarketplace) { - if (commandExists('codex')) defaultRunner('codex', ['plugin', 'marketplace', 'remove', 'automagik']); - if (commandExists('claude')) defaultRunner('claude', ['plugin', 'marketplace', 'remove', 'automagik']); + if (commandExists('codex')) runner('codex', ['plugin', 'marketplace', 'remove', 'automagik']); + if (commandExists('claude')) runner('claude', ['plugin', 'marketplace', 'remove', 'automagik']); } }📝 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.export function removeRuntimeIntegrations(removeMarketplace = false, runner: CommandRunner = defaultRunner): void { const codexHome = getCodexHome(); const agentsDir = join(codexHome, 'agents'); if (existsSync(agentsDir)) { for (const name of readdirSync(agentsDir)) { if (!/^genie-.+\.toml$/.test(name)) continue; const path = join(agentsDir, name); try { if (readFileSync(path, 'utf8').startsWith('# Managed by Genie.')) unlinkSync(path); } catch { // Preserve unreadable or user-modified files. } } } if (commandExists('codex')) runner('codex', ['plugin', 'remove', 'genie@automagik']); if (commandExists('claude')) runner('claude', ['plugin', 'uninstall', 'genie@automagik']); if (removeMarketplace) { if (commandExists('codex')) runner('codex', ['plugin', 'marketplace', 'remove', 'automagik']); if (commandExists('claude')) runner('claude', ['plugin', 'marketplace', 'remove', 'automagik']); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/runtime-integrations.ts` around lines 175 - 195, Make removeRuntimeIntegrations testable by adding an injectable CommandRunner parameter, matching installRuntimeIntegrations. Default it to defaultRunner, then replace every direct defaultRunner call for plugin and marketplace removal with the injected runner while preserving existing behavior and arguments.src/term-commands/init.test.ts (1)
240-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Good coverage of Codex MCP fallback merge/remove edge cases.
The two tests cover the critical contracts: idempotency (skip when user-owned
[mcp_servers.genie]exists without markers) and selectivity (remove only the marker-owned block, preserving[mcp_servers.other]).One gap: the integration test at lines 223-226 uses
slice(0, 2)to avoid environment-dependent Codex entries, so theregisterMcpConfigs→mergeCodexMcpFallbackpath isn't exercised end-to-end when Codex is present. Consider adding a test that mocksBun.which('codex')andcodexHasGeniePlugin()to verify the Codex fallback entry appears in the init result.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/term-commands/init.test.ts` around lines 240 - 263, The tests cover direct fallback behavior but not the end-to-end Codex registration path. Add an integration test around registerMcpConfigs that mocks Bun.which('codex') as available and codexHasGeniePlugin() as absent, then assert the init result includes the Codex fallback entry.Source: Learnings
src/term-commands/init.ts (1)
244-260: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
mergeCodexMcpFallbackhardcodesargsand doesn't handle orphaned begin markers.Two issues in this function:
Line 249:
args = ["mcp"]is hardcoded instead of usingentry.args. Theentryparameter carries anargsfield that's silently ignored. If the MCP server args ever change, this function would write stale values.Lines 250-255: If the config file has a
# BEGIN GENIE MCP FALLBACKmarker but no matching# END GENIE MCP FALLBACK(corrupted or manually edited),start >= 0butfinish < 0, so the else branch appends a new block. The result has two begin markers and one end marker — malformed TOML.🔧 Proposed fixes
export function mergeCodexMcpFallback(configPath: string, entry: McpServerEntry): ArtifactAction { const raw = existsSync(configPath) ? readFileSync(configPath, 'utf8') : ''; const begin = '# BEGIN GENIE MCP FALLBACK'; const end = '# END GENIE MCP FALLBACK'; if (raw.includes('[mcp_servers.genie]') && !raw.includes(begin)) return 'skipped'; - const block = `${begin}\n[mcp_servers.genie]\ncommand = ${JSON.stringify(entry.command)}\nargs = ["mcp"]\n${end}`; + const block = `${begin}\n[mcp_servers.genie]\ncommand = ${JSON.stringify(entry.command)}\nargs = ${JSON.stringify(entry.args)}\n${end}`; const start = raw.indexOf(begin); const finish = start >= 0 ? raw.indexOf(end, start) : -1; const updated = - start >= 0 && finish >= 0 + start >= 0 && finish >= 0 ? `${raw.slice(0, start)}${block}${raw.slice(finish + end.length)}` - : `${raw}${raw.length > 0 && !raw.endsWith('\n') ? '\n' : ''}\n${block}\n`; + : start >= 0 + ? `${raw.slice(0, start)}${block}\n` + : `${raw}${raw.length > 0 && !raw.endsWith('\n') ? '\n' : ''}\n${block}\n`; if (updated === raw) return 'skipped'; mkdirSync(dirname(configPath), { recursive: true }); - writeFileSync(configPath, updated); + writeFileSync(configPath, updated, 'utf8'); return raw.length === 0 ? 'created' : 'updated'; }📝 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.export function mergeCodexMcpFallback(configPath: string, entry: McpServerEntry): ArtifactAction { const raw = existsSync(configPath) ? readFileSync(configPath, 'utf8') : ''; const begin = '# BEGIN GENIE MCP FALLBACK'; const end = '# END GENIE MCP FALLBACK'; if (raw.includes('[mcp_servers.genie]') && !raw.includes(begin)) return 'skipped'; const block = `${begin}\n[mcp_servers.genie]\ncommand = ${JSON.stringify(entry.command)}\nargs = ${JSON.stringify(entry.args)}\n${end}`; const start = raw.indexOf(begin); const finish = start >= 0 ? raw.indexOf(end, start) : -1; const updated = start >= 0 && finish >= 0 ? `${raw.slice(0, start)}${block}${raw.slice(finish + end.length)}` : start >= 0 ? `${raw.slice(0, start)}${block}\n` : `${raw}${raw.length > 0 && !raw.endsWith('\n') ? '\n' : ''}\n${block}\n`; if (updated === raw) return 'skipped'; mkdirSync(dirname(configPath), { recursive: true }); writeFileSync(configPath, updated, 'utf8'); return raw.length === 0 ? 'created' : 'updated'; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/term-commands/init.ts` around lines 244 - 260, Update mergeCodexMcpFallback to serialize entry.args instead of the hardcoded ["mcp"], preserving the expected TOML array format. Handle an orphaned begin marker by replacing or repairing the existing incomplete fallback block rather than appending another block; ensure the resulting configuration contains exactly one complete BEGIN/END marker pair.
…ble timeout (B2, F9)
…rker, win parity (B1, B3, B6)
… as .genie-kept (B4)
… integration map (B7)
Summary
Testing
Summary by CodeRabbit
New Features
genie launchcan now select the configured runtime and safely handle prompts.Bug Fixes
Documentation
Chores