Skip to content

fix(group-chat): pass global shell env vars to moderator/participant spawns#887

Merged
pedramamini merged 2 commits intoRunMaestro:mainfrom
josecsotomorales:fix/group-chat-moderator-global-env-vars
Apr 25, 2026
Merged

fix(group-chat): pass global shell env vars to moderator/participant spawns#887
pedramamini merged 2 commits intoRunMaestro:mainfrom
josecsotomorales:fix/group-chat-moderator-global-env-vars

Conversation

@josecsotomorales
Copy link
Copy Markdown
Contributor

@josecsotomorales josecsotomorales commented Apr 23, 2026

Fixes #886.

Summary

Group Chat spawns in group-chat-router.ts were calling processManager.spawn() without shellEnvVars, so environment variables set in Settings → Shell Configuration → Global Environment Variables were not merged into the child process environment. Individual (1-on-1) chats go through ipc/handlers/process.ts which already passes shellEnvVars correctly, so this bug only affected Group Chats.

In practice, users relying on a globally-configured ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL) saw the moderator immediately fail with a synthetic auth-failed stub from the Claude CLI:

{ "model": "<synthetic>", "error": "authentication_failed" }

because envBuilder.ts strips CLAUDECODE / CLAUDE_CODE_* / ELECTRON_* and the merged shellEnvVars layer was missing, so the CLI launched with no credentials.

Changes

  • src/main/group-chat/group-chat-router.ts
    • Add getGlobalShellEnvVars() helper that reads from getSettingsStore() (mirrors ipc/handlers/process.ts:230).
    • Pass shellEnvVars: getGlobalShellEnvVars() at all four processManager.spawn() sites: moderator (initial routing), participant, moderator synthesis, and participant recovery.
  • src/main/group-chat/group-chat-moderator.ts
    • Add optional shellEnvVars?: Record<string, string> to the IProcessManager.spawn config type so the router's calls typecheck. Matches the existing ProcessConfig.shellEnvVars field.
  • src/__tests__/main/group-chat/group-chat-router.test.ts
    • Mock stores/getters so the router tests don't blow up on uninitialized stores (the 17 spawn-path tests previously passed because the router never called getSettingsStore(); now they need it stubbed).

Test plan

  • npm run lint — clean
  • npx vitest run src/__tests__/main/group-chat/group-chat-router.test.ts — 61/61 pass
  • npx vitest run (full suite) — 22521/22522 pass; the single failure (stats/integration.test.ts > electron-rebuild verification for better-sqlite3) is a native-module env check unrelated to this change and fails on main in environments where electron-rebuild's gyp build can't complete.
  • Manual: set ANTHROPIC_API_KEY in Settings → Shell Configuration, start a Group Chat with Claude Code as moderator, confirm moderator responds without authentication_failed.

Notes

  • This is the class of bug catalogued in Centralized Process Spawning #317 ("Centralized Process Spawning") — the group-chat spawn sites were apparently missed in that pass. A follow-up centralizing all eight spawn sites behind a single helper would prevent recurrence, but I've kept this PR scoped to the minimal behavioural fix.
  • Precedence preserved: customEnvVars (per-agent / per-moderator) still wins over shellEnvVars (global) per envBuilder.ts:199-238.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added targeted tests to verify propagation and merging of global shell environment variables into group-chat spawn flows, with per-test isolation to prevent state leakage.
  • Refactor

    • Consistently apply globally configured shell environment variables across moderator, synthesis, and participant subprocesses; per-session custom variables take precedence on conflict.

…spawns

Group Chat spawns in group-chat-router.ts were calling
processManager.spawn() without shellEnvVars, so environment variables
set in Settings → Shell Configuration → Global Environment Variables
were not merged into the child process environment for the moderator,
participants, synthesis, or recovery.

Individual (1-on-1) chats go through ipc/handlers/process.ts which
correctly reads settingsStore.get('shellEnvVars') and passes it to
spawn(), so this bug only affected Group Chats.

In practice this meant users relying on a globally-configured
ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL)
saw the moderator immediately fail with a synthetic auth-failed
stub from the Claude CLI:

    { "model": "<synthetic>", "error": "authentication_failed" }

because envBuilder.ts strips CLAUDECODE/CLAUDE_CODE_*/ELECTRON_* and
the merged shellEnvVars layer was missing, so the CLI launched with
no credentials.

Changes:
- Add getGlobalShellEnvVars() helper that reads from getSettingsStore()
  (mirrors ipc/handlers/process.ts:230).
- Pass shellEnvVars at all four processManager.spawn() sites in
  group-chat-router.ts (moderator, participant, synthesis, recovery).
- Add shellEnvVars to the IProcessManager spawn config type in
  group-chat-moderator.ts to match ProcessConfig.
- Mock stores/getters in group-chat-router.test.ts so tests that
  exercise these spawn paths don't blow up on uninitialized stores.

Fixes RunMaestro#886
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2929e5d6-c63c-46a7-a2e7-aaf70578d818

📥 Commits

Reviewing files that changed from the base of the PR and between 4077ce0 and daf43cf.

📒 Files selected for processing (3)
  • src/__tests__/main/group-chat/group-chat-router.test.ts
  • src/main/group-chat/group-chat-router.ts
  • src/main/ipc/handlers/groupChat.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/main/group-chat/group-chat-router.test.ts
  • src/main/group-chat/group-chat-router.ts

📝 Walkthrough

Walkthrough

Reads global shell environment variables from Settings and passes them into group-chat spawn paths; merges global vars with per-agent custom env (per-agent wins) and ensures SSH-wrapped spawns receive merged envs.

Changes

Cohort / File(s) Summary
Tests / Mocks
src/__tests__/main/group-chat/group-chat-router.test.ts
Adds a Vitest mock for getSettingsStore().get('shellEnvVars', ...), backed by per-test mockedShellEnvVars, and tests that global shellEnvVars propagate into spawn options and SSH wrapping (with precedence rules).
Spawn Interface
src/main/group-chat/group-chat-moderator.ts
Extended IProcessManager.spawn config to accept optional shellEnvVars?: Record<string,string>.
Router / Merge Logic
src/main/group-chat/group-chat-router.ts
Reads settings-backed shellEnvVars, centralizes merge of global + per-agent customEnvVars (agent-level wins), updates multiple group-chat spawn call sites to pass both customEnvVars (merged) and shellEnvVars into processManager.spawn, and ensures SSH wrapping receives merged envs.
IPC / ProcessManager Handler
src/main/ipc/handlers/groupChat.ts
Expands internal GenericProcessManager spawn config to accept shellEnvVars plus shell/prompt-related flags (promptArgs, shell, runInShell, sendPromptViaStdin, sendPromptViaStdinRaw).

Sequence Diagram(s)

sequenceDiagram
  participant Router as GroupChat Router
  participant Settings as Settings Store
  participant ProcMgr as ProcessManager
  participant SSH as wrapSpawnWithSsh

  Router->>Settings: get('shellEnvVars', {})
  Router->>Router: merge(globalShellEnvVars, agent.customEnvVars)\n(agent customEnvVars take precedence)
  Router->>ProcMgr: spawn({ customEnvVars: merged, shellEnvVars: globalShellEnvVars, ... })
  ProcMgr->>SSH: wrapSpawnWithSsh(..., customEnvVars: merged, customEnvVarsForSsh: merged)
  SSH->>ProcMgr: returns wrapped spawn
  ProcMgr->>Router: spawn started / pid
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped to Settings, dug up the shells,
Pushed them to spawns and fixed auth spells.
Moderator now greets keys without fright,
Env-vars snug, processes alight —
A tiny hop, and everything's right. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: passing global shell environment variables to moderator and participant spawns in group chat, which is the core fix for the reported bug.
Linked Issues check ✅ Passed The PR fully addresses issue #886 by reading global shell env vars and passing them to all four processManager.spawn() sites, merging them into SSH wrap inputs with proper precedence, and adding regression-proofing tests.
Out of Scope Changes check ✅ Passed All changes are directly in scope: adding shellEnvVars parameter to IProcessManager interface, retrieving and passing global shell env vars at spawn sites, merging into SSH inputs, and writing focused tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR fixes group-chat spawns missing global shell env vars (e.g. ANTHROPIC_API_KEY set in Settings → Shell Configuration) by adding a getGlobalShellEnvVars() helper and threading shellEnvVars into all four processManager.spawn() call sites in group-chat-router.ts. SSH paths additionally use mergeGlobalShellWithCustomEnv() to embed the globals into the SSH stdin script before calling wrapSpawnWithSsh(), mirroring the existing pattern in ipc/handlers/process.ts. The accompanying test additions cover moderator, participant, and SSH spawn paths and correctly assert precedence (per-agent customEnvVars win over globals).

Confidence Score: 5/5

Safe to merge — targeted, well-tested fix with no logic regressions.

All four spawn sites are consistently updated, the SSH merge pattern correctly mirrors the individual-chat handler, per-agent precedence is preserved, and new tests directly verify the fix including the SSH precedence case. No P0/P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
src/main/group-chat/group-chat-router.ts Core fix: adds getGlobalShellEnvVars() helper and passes shellEnvVars to all four spawn sites; SSH paths correctly pre-merge globals into customEnvVars via mergeGlobalShellWithCustomEnv() before calling wrapSpawnWithSsh()
src/main/group-chat/group-chat-moderator.ts Type-only change: adds optional shellEnvVars field to IProcessManager.spawn config interface to match new router call sites
src/tests/main/group-chat/group-chat-router.test.ts Adds stores/getters mock with per-test reset and three new shellEnvVars propagation tests covering moderator, participant, and SSH paths with precedence verification
src/main/ipc/handlers/groupChat.ts Type-only additions to GenericProcessManager spawn interface: shellEnvVars, promptArgs, shell, runInShell, sendPromptViaStdin, sendPromptViaStdinRaw — no spawn call changes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[routeUserMessage / routeModeratorResponse / respawnParticipantWithRecovery] --> B{SSH remote configured?}

    B -- Yes --> C["mergeGlobalShellWithCustomEnv(perAgentEnvVars)\n= { ...globalShellEnvVars, ...perAgentEnvVars }"]
    C --> D["wrapSpawnWithSsh({ customEnvVars: merged })"]
    D --> E["spawnEnvVars = sshWrapped.customEnvVars"]
    E --> F["processManager.spawn({ customEnvVars: spawnEnvVars,\n  shellEnvVars: getGlobalShellEnvVars() })"]

    B -- No --> G["spawnEnvVars = perAgentEnvVars"]
    G --> F

    F --> H["envBuilder merges shellEnvVars + customEnvVars\n(per-agent wins on conflict)"]
    H --> I[Child process launched with full env]
Loading

Reviews (2): Last reviewed commit: "fix(group-chat): merge global shell env ..." | Re-trigger Greptile

Comment thread src/__tests__/main/group-chat/group-chat-router.test.ts
Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
src/main/group-chat/group-chat-router.ts (1)

778-827: ⚠️ Potential issue | 🟠 Major

Merge global shell env vars into SSH wrapping input with custom env taking precedence.

The wrapSpawnWithSsh() function correctly embeds customEnvVars into the remote SSH command via buildRemoteCommand(). However, getGlobalShellEnvVars() is not being merged into the customEnvVars passed to the wrapper—it's only passed to processManager.spawn() after wrapping. For SSH sessions, this means global variables like ANTHROPIC_API_KEY are applied only to the local ssh process, not forwarded to the remote agent.

Merge getGlobalShellEnvVars() into the customEnvVars input to wrapSpawnWithSsh() at all four locations (lines 778–827, 1266–1319, 1690–1739, 1892–1938), with session/moderator custom env vars taking precedence. Custom env vars should override globals where both are set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-router.ts` around lines 778 - 827, The SSH
wrapper call is not receiving global shell env vars so globals (e.g.,
ANTHROPIC_API_KEY) aren't forwarded to the remote process; before calling
wrapSpawnWithSsh (the calls that build the remote command), merge
getGlobalShellEnvVars() into the customEnvVars passed in so that the merged
object is passed as customEnvVars to wrapSpawnWithSsh — ensure the merge uses
session/moderator custom vars (the existing
configResolution.effectiveCustomEnvVars or getCustomEnvVarsCallback(...)) to
override global keys when present; apply this merge in the wrapSpawnWithSsh
invocations that set spawnEnvVars/spawnCommand/spawnArgs (the block using
chat.moderatorConfig?.sshRemoteConfig and the three other similar blocks
referenced in the review).
🧹 Nitpick comments (1)
src/__tests__/main/group-chat/group-chat-router.test.ts (1)

49-55: Make the settings mock configurable enough to test the regression.

This mock keeps the suite isolated, but because it always returns the default, the new behavior would still pass if group-chat-router.ts used the wrong key or stopped passing shellEnvVars. Consider returning a sentinel for shellEnvVars in at least one moderator and participant spawn test and asserting it reaches mockProcessManager.spawn.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/group-chat/group-chat-router.test.ts` around lines 49 -
55, The settings mock always returning the default hides regressions; update the
vi.mock for getSettingsStore so get(key, defaultValue) returns a configurable
value when key === 'shellEnvVars' (e.g., a sentinel object) but falls back to
default for other keys, then in the moderator and participant spawn tests set
that sentinel in the mocked settings and add assertions that
mockProcessManager.spawn was called with an env containing that sentinel under
shellEnvVars, targeting the getSettingsStore mock and mockProcessManager.spawn
to validate the value flows through group-chat-router.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/group-chat/group-chat-moderator.ts`:
- Around line 31-32: The GenericProcessManager interface is missing six optional
fields used by spawn() in the router; update the GenericProcessManager
definition to add these optional properties with appropriate
types—shellEnvVars?: Record<string,string>, promptArgs?: string[], shell?:
string, runInShell?: boolean, sendPromptViaStdin?: boolean, and
sendPromptViaStdinRaw?: boolean—so the interface matches how spawn() is invoked
(e.g., in group-chat-router.ts) and prevents type mismatches.

---

Outside diff comments:
In `@src/main/group-chat/group-chat-router.ts`:
- Around line 778-827: The SSH wrapper call is not receiving global shell env
vars so globals (e.g., ANTHROPIC_API_KEY) aren't forwarded to the remote
process; before calling wrapSpawnWithSsh (the calls that build the remote
command), merge getGlobalShellEnvVars() into the customEnvVars passed in so that
the merged object is passed as customEnvVars to wrapSpawnWithSsh — ensure the
merge uses session/moderator custom vars (the existing
configResolution.effectiveCustomEnvVars or getCustomEnvVarsCallback(...)) to
override global keys when present; apply this merge in the wrapSpawnWithSsh
invocations that set spawnEnvVars/spawnCommand/spawnArgs (the block using
chat.moderatorConfig?.sshRemoteConfig and the three other similar blocks
referenced in the review).

---

Nitpick comments:
In `@src/__tests__/main/group-chat/group-chat-router.test.ts`:
- Around line 49-55: The settings mock always returning the default hides
regressions; update the vi.mock for getSettingsStore so get(key, defaultValue)
returns a configurable value when key === 'shellEnvVars' (e.g., a sentinel
object) but falls back to default for other keys, then in the moderator and
participant spawn tests set that sentinel in the mocked settings and add
assertions that mockProcessManager.spawn was called with an env containing that
sentinel under shellEnvVars, targeting the getSettingsStore mock and
mockProcessManager.spawn to validate the value flows through
group-chat-router.ts.
🪄 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: CHILL

Plan: Pro

Run ID: 759896af-b687-459a-9b7c-4bf2d60de108

📥 Commits

Reviewing files that changed from the base of the PR and between 8484845 and 4077ce0.

📒 Files selected for processing (3)
  • src/__tests__/main/group-chat/group-chat-router.test.ts
  • src/main/group-chat/group-chat-moderator.ts
  • src/main/group-chat/group-chat-router.ts

Comment on lines +31 to +32
/** Global shell env vars from Settings → Shell Configuration (merged by envBuilder). */
shellEnvVars?: Record<string, string>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find process-manager-like spawn config interfaces that may need shellEnvVars added.
rg -n -C8 'interface .*ProcessManager|spawn\(config:\s*\{' --iglob '*.ts'

Repository: RunMaestro/Maestro

Length of output: 5080


🏁 Script executed:

#!/bin/bash
# Get full IProcessManager definition in group-chat-moderator.ts
echo "=== IProcessManager in group-chat-moderator.ts ==="
sed -n '21,40p' src/main/group-chat/group-chat-moderator.ts

echo ""
echo "=== GenericProcessManager in groupChat.ts ==="
sed -n '129,150p' src/main/ipc/handlers/groupChat.ts

echo ""
echo "=== GroomingProcessManager in context-groomer.ts ==="
sed -n '26,45p' src/main/utils/context-groomer.ts

Repository: RunMaestro/Maestro

Length of output: 2190


🏁 Script executed:

#!/bin/bash
# Check how GenericProcessManager is used in groupChat.ts
echo "=== Search for GenericProcessManager usage ==="
rg -n "GenericProcessManager" src/main/ipc/handlers/groupChat.ts -A 3 -B 3

echo ""
echo "=== Check moderatorRunner and agentRunner types ==="
rg -n "moderatorRunner|agentRunner" src/main/ipc/handlers/groupChat.ts -A 2 -B 2

echo ""
echo "=== Check if GenericProcessManager is instantiated or casted ==="
grep -n "new\|as GenericProcessManager\|as IProcessManager" src/main/ipc/handlers/groupChat.ts || echo "No direct instantiation found"

Repository: RunMaestro/Maestro

Length of output: 2495


🏁 Script executed:

#!/bin/bash
# Check where getProcessManager is called and what's passed to spawn()
echo "=== Find getProcessManager() calls ==="
rg -n "getProcessManager\(\)" src/main/ipc/handlers/groupChat.ts -A 5

echo ""
echo "=== Look for spawn() calls in groupChat.ts ==="
rg -n "\.spawn\(" src/main/ipc/handlers/groupChat.ts -A 8

echo ""
echo "=== Check GroupChatModerator instantiation ==="
rg -n "new GroupChatModerator\|GroupChatModerator\(" src/main/ipc/handlers/groupChat.ts -B 2 -A 5

Repository: RunMaestro/Maestro

Length of output: 3785


🏁 Script executed:

#!/bin/bash
# Find spawnModerator and related spawn calls
echo "=== Search for spawnModerator function ==="
rg -n "function spawnModerator|const spawnModerator|export.*spawnModerator" src/main/ipc/handlers/groupChat.ts -A 20

echo ""
echo "=== Look for spawn calls within groupChat functions ==="
rg -n "processManager\.spawn\|\.spawn\(" src/main/ipc/handlers/groupChat.ts -B 3 -A 10

echo ""
echo "=== Check GroupChatModerator for spawn() calls ==="
rg -n "this\.processManager\.spawn\|processManager\.spawn" src/main/group-chat/group-chat-moderator.ts -B 2 -A 10

Repository: RunMaestro/Maestro

Length of output: 213


🏁 Script executed:

#!/bin/bash
# Simpler search for spawnModerator
echo "=== Plain search for spawnModerator ==="
grep -rn "spawnModerator" src/main/ipc/handlers/groupChat.ts src/main/group-chat/

echo ""
echo "=== Look for spawn in group-chat directory ==="
grep -n "\.spawn(" src/main/group-chat/group-chat-moderator.ts | head -20

echo ""
echo "=== Check what functions exist in groupChat.ts before line 200 ==="
sed -n '1,200p' src/main/ipc/handlers/groupChat.ts | grep -n "^\(function\|const\|export\)" | tail -20

Repository: RunMaestro/Maestro

Length of output: 1781


🏁 Script executed:

#!/bin/bash
# Read spawnModerator implementation
echo "=== spawnModerator function ==="
sed -n '154,250p' src/main/group-chat/group-chat-moderator.ts

echo ""
echo "=== Look for spawn calls in the entire function ==="
grep -n "\.spawn(" src/main/group-chat/group-chat-moderator.ts -B 5 -A 15

Repository: RunMaestro/Maestro

Length of output: 3163


🏁 Script executed:

#!/bin/bash
# Find actual spawn calls in group-chat-router.ts
echo "=== Spawn calls in group-chat-router.ts ==="
grep -n "\.spawn(" src/main/group-chat/group-chat-router.ts -B 5 -A 20 | head -80

echo ""
echo "=== Also check for shell-related fields ==="
grep -n "shellEnvVars\|shell:\|runInShell" src/main/group-chat/ -r

Repository: RunMaestro/Maestro

Length of output: 5304


🏁 Script executed:

#!/bin/bash
# Check if there's any type casting or overriding of GenericProcessManager
echo "=== Where is processManager typed as GenericProcessManager vs IProcessManager? ==="
grep -n "processManager.*GenericProcessManager\|processManager.*IProcessManager" src/main/ipc/handlers/groupChat.ts

echo ""
echo "=== Check the actual type of processManager in group-chat-router.ts ==="
head -150 src/main/group-chat/group-chat-router.ts | grep -n "import.*ProcessManager\|ProcessManager\|processManager"

echo ""
echo "=== Verify the actual spawn call parameter types ==="
sed -n '817,834p' src/main/group-chat/group-chat-router.ts

Repository: RunMaestro/Maestro

Length of output: 927


🏁 Script executed:

#!/bin/bash
# Final check: verify what fields are used in spawn calls to ensure completeness
echo "=== All unique fields passed to spawn() in group-chat-router.ts ==="
grep -o "^\s*[a-zA-Z]*:" src/main/group-chat/group-chat-router.ts | grep -A30 "\.spawn({" | sort -u

echo ""
echo "=== Compare against GenericProcessManager definition ==="
sed -n '129,150p' src/main/ipc/handlers/groupChat.ts

echo ""
echo "=== Compare against IProcessManager definition ==="
sed -n '21,45p' src/main/group-chat/group-chat-moderator.ts

Repository: RunMaestro/Maestro

Length of output: 1763


Sync GenericProcessManager interface with IProcessManager spawn config.

GenericProcessManager is missing 6 optional fields that actual spawn() calls in group-chat-router.ts use: shellEnvVars, promptArgs, shell, runInShell, sendPromptViaStdin, and sendPromptViaStdinRaw. Adding these to GenericProcessManager will prevent type mismatches and keep the interface contracts aligned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-moderator.ts` around lines 31 - 32, The
GenericProcessManager interface is missing six optional fields used by spawn()
in the router; update the GenericProcessManager definition to add these optional
properties with appropriate types—shellEnvVars?: Record<string,string>,
promptArgs?: string[], shell?: string, runInShell?: boolean,
sendPromptViaStdin?: boolean, and sendPromptViaStdinRaw?: boolean—so the
interface matches how spawn() is invoked (e.g., in group-chat-router.ts) and
prevents type mismatches.

@pedramamini
Copy link
Copy Markdown
Collaborator

Thanks for the fix @josecsotomorales — nice diagnosis and the local-spawn path is clearly correct. Before we merge I'd like to get the SSH case sorted as well.

CodeRabbit flagged this and after walking through the code I think it's a real gap: in all four processManager.spawn() sites you're passing shellEnvVars: getGlobalShellEnvVars(), but for SSH sessions the spawn has already been transformed by wrapSpawnWithSsh() at that point. That helper embeds only customEnvVars into the remote SSH stdin script (src/main/utils/ssh-spawn-wrapper.ts:171) and then returns customEnvVars: undefined (line 189). So shellEnvVars only lands on the local ssh client process — ANTHROPIC_API_KEY never makes it to the remote agent, and users with SSH-configured group chats hit the same authentication_failed you're fixing.

The individual-chat path handles this at src/main/ipc/handlers/process.ts:437:

const mergedSshEnvVars = { ...globalShellEnvVars, ...(effectiveCustomEnvVars || {}) };

…and feeds that merged object into the SSH command builder. Mirroring that here — merging getGlobalShellEnvVars() into the customEnvVars you pass to wrapSpawnWithSsh() at all four call sites, with the per-agent effectiveCustomEnvVars taking precedence — would close the same gap for SSH group chats.

Also worth tightening the test: the current getSettingsStore mock always returns the default, so a regression that stops threading shellEnvVars through would still pass. Returning a sentinel map and asserting it reaches mockProcessManager.spawn for at least one moderator and one participant path would catch that.

Lint/tests all look clean and the scoping is good — just need the SSH path before this goes in. Thanks again!

Addresses review feedback on the initial commit:

1. SSH path was still broken
   wrapSpawnWithSsh() embeds only customEnvVars into the remote SSH stdin
   script (ssh-spawn-wrapper.ts:171) and then returns customEnvVars: undefined
   (line 189). The previous patch only added shellEnvVars to the *local*
   processManager.spawn() call — for SSH sessions that lands on the local
   ssh client process, not the remote agent, so group chats over SSH
   continued to hit authentication_failed.

   Mirror the individual-chat pattern at ipc/handlers/process.ts:437 by
   merging global shell env vars into customEnvVars BEFORE calling
   wrapSpawnWithSsh(), with per-agent effectiveCustomEnvVars taking
   precedence. Applied at all four SSH wrap sites (moderator, participant,
   synthesis, recovery) via a new mergeGlobalShellWithCustomEnv() helper.

2. GenericProcessManager type widened
   The narrower GenericProcessManager interface in ipc/handlers/groupChat.ts
   was missing shellEnvVars and several other optional spawn-config fields
   already used by the router (promptArgs, shell, runInShell,
   sendPromptViaStdin, sendPromptViaStdinRaw). Align it with IProcessManager
   / ProcessConfig.

3. Test tightening
   The previous settings-store mock always returned the default, so a
   regression that stopped threading shellEnvVars through would still pass.
   Drive the mock from a mutable mockedShellEnvVars sentinel (reset in
   beforeEach), and add three new assertions:
     - moderator local spawn receives the sentinel as shellEnvVars
     - participant local spawn receives the sentinel as shellEnvVars
     - SSH participant spawn's wrapSpawnWithSsh input has the sentinel
       merged into customEnvVars, with per-session customEnvVars winning
       over the global for the same key

Refs PR review feedback (pedramamini, coderabbitai, greptile-apps).
@josecsotomorales
Copy link
Copy Markdown
Contributor Author

Thanks for the review @pedramamini — great catch on the SSH path. Pushed daf43cf8 addressing all three review items:

1. SSH gap closed. Added mergeGlobalShellWithCustomEnv() helper and applied it at all four wrapSpawnWithSsh() call sites (moderator / participant / synthesis / recovery). Per-agent effectiveCustomEnvVars takes precedence over globals, mirroring ipc/handlers/process.ts:437.

2. GenericProcessManager widened (CodeRabbit's suggestion). The interface in ipc/handlers/groupChat.ts was missing shellEnvVars plus promptArgs / shell / runInShell / sendPromptViaStdin / sendPromptViaStdinRaw — already used by the router. Aligned with IProcessManager / ProcessConfig.

3. Test sentinel (Greptile's nit). The mock now reads from a mutable mockedShellEnvVars (reset in beforeEach) rather than always returning the default. Three new assertions:

  • moderator local spawn receives the sentinel as shellEnvVars
  • participant local spawn receives the sentinel as shellEnvVars
  • SSH-participant path's wrapSpawnWithSsh input has the sentinel merged into customEnvVars, with per-session vars winning for the same key

Lint clean, 64/64 router tests pass (3 new), full suite 22524/22525 (same unrelated better-sqlite3 electron-rebuild env check fails locally regardless of branch). Ready for another look when you have a moment.

@josecsotomorales
Copy link
Copy Markdown
Contributor Author

@greptileai

@pedramamini pedramamini merged commit 6f53077 into RunMaestro:main Apr 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Group chat moderator ignores global shell env vars (ANTHROPIC_API_KEY not merged)

2 participants