Repo-local .honcho configuration and per-repo sessions - #12
Conversation
WalkthroughThe PR adds repository-local Honcho configuration overlays, per-repository session and memory-key handling, and a local stdio MCP server. The CLI, hooks, connector, tests, and documentation now use directory-aware configuration. ChangesRepository-local configuration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Codex
participant CLI
participant LocalMCP
participant Config
participant Honcho
Codex->>CLI: run install or mcp
CLI->>LocalMCP: launch local stdio server
LocalMCP->>Config: loadConfig(cwd)
LocalMCP->>Honcho: execute selected tool
Honcho-->>LocalMCP: return result
LocalMCP-->>Codex: return MCP response
Possibly related PRs
Suggested reviewers: Poem
🚥 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 |
# Conflicts: # CHANGELOG.md
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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 `@bin/codex-honcho.ts`:
- Around line 115-117: Update the configuration flow around loadConfig and
installMcpServer: store the loaded config, then install the MCP server only when
config?.enabled is true. Preserve the existing mcpInvoke(entry) registration and
success message for enabled configurations, while skipping installation when
enabled is false or no config is loaded.
In `@src/config.ts`:
- Around line 319-325: Update memoryKey to include localConfig.root in the local
key alongside the workspace and key, ensuring repositories sharing a workspace
remain isolated when sessionId is absent. Preserve the existing non-local key
behavior and the current sessionName fallback.
- Around line 181-184: Update applyLocalConfig’s LOCAL_FIELDS overlay to
normalize and validate each local value before assigning it: coerce
enabled-style fields using the existing boolean semantics, accept
sessionStrategy only when it matches an allowed value, and pass local peerName
through resolvePeerName just as the global configuration path does. Do not write
raw localField results through the unknown cast.
- Around line 165-191: Update applyLocalConfig so a repository-local endpoint
override is applied only when the local configuration also provides its own
apiKey; otherwise preserve the inherited endpoint while allowing other local
fields to merge. Ensure the existing environment-key override and
effective.apiKey validation remain intact, and prevent localField(raw,
"endpoint") from replacing the trusted endpoint when the key would be inherited
from base.
In `@src/mcp.ts`:
- Around line 150-153: The Honcho construction in src/mcp.ts lines 150-153 must
prevent repository-local endpoint configuration from inheriting global or
environment credentials: restrict local endpoint overrides or require an
explicit trusted opt-in before passing credentials to Honcho, while preserving
normal trusted configurations. Update README.md lines 138-151 to remove the
unsafe inheritance claim and document the enforced endpoint and credential trust
rule.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a4678da9-3397-4ecb-8294-d1b14d86451d
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (16)
.gitignoreCHANGELOG.mdREADME.mdbin/codex-honcho.tsbuild.mjssrc/config.tssrc/connectors/mcp.tssrc/hooks/flush.tssrc/hooks/observe.tssrc/hooks/prompt.tssrc/hooks/recall.tssrc/hooks/writeback.tssrc/mcp.tstest/config.test.tstest/connectors/mcp.test.tstest/mcp.test.ts
| if (loadConfig(process.cwd())) { | ||
| installMcpServer(mcpInvoke(entry)); | ||
| console.log(`Registered Honcho MCP server → ${DEFAULT_CONFIG_PATH}`); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline src/config.ts --items all
rg -n -C 5 'loadConfig|enabled|apiKey|resolveFileConfig' src/config.ts testRepository: plastic-labs/codex-honcho
Length of output: 30837
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== bin/codex-honcho outline =="
ast-grep outline bin/codex-honcho.ts --items all || true
echo "== relevant bin/codex-honcho.ts lines =="
sed -n '1,180p' bin/codex-honcho.ts | cat -n
echo "== mcp connectors outline/search =="
fd -i '.*mcp.*' .
rg -n -C 4 'runMcpServer|installMcpServer|loadConfig\(|config\.enabled|enabled:' .Repository: plastic-labs/codex-honcho
Length of output: 31773
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== config resolver behavior probe =="
python3 - <<'PY'
from pathlib import Path
import re
text = Path('src/config.ts').read_text()
checks = {
"default enabled from file": ",enabled: ",
"local enabled is a local field": '"enabled"',
"default truthiness": "('enabled' in raw) === false)",
}
for name, needle in checks.items():
print(f"{name}: {needle in text} ({text.find(needle)})")
apply = re.search(r"function applyLocalConfig\([\s\S]*?\n\}", text).group(0)
enabled_assign_in_apply = bool(re.search(r"\(effective as unknown[\s\S]*?\[\"enabled\"\][\s\S]*?local\.", apply))
print("local assigned into effective.enabled:", enabled_assign_in_apply)
PY
echo "== deterministic semantic extraction =="
python3 - <<'PY'
exec("function resolveFileConfig(raw) {\n const HOST = 'codex';\n const apiKey = process.env.HONCHO_API_KEY || raw.hosts?.[HOST]?.apiKey || raw.apiKey;\n if (!apiKey) return null;\n return { apiKey, enabled: (raw.hosts?.codex?.enabled ?? raw.enabled) !== false };\n}")
cases = [
{},
{"apiKey": "k", "peerName": "u"},
{"apiKey": "k", "peerName": "u", "enabled": false},
{"hosts": {"codex": {"apiKey": "k", "enabled": false}}},
{"hosts": {"codex": {"apiKey": "k"}}},
]
for raw in cases:
try:
process={"HONCHO_API_KEY":""}
out=resolveFileConfig(raw)
print(raw, "=>", out)
except Exception as e:
print(raw, "=> ERROR", e)
PYRepository: plastic-labs/codex-honcho
Length of output: 578
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '92,195p' src/config.ts | cat -n
node - <<'JS'
function resolveFileConfig(raw) {
const HOST = "codex";
const apiKey = process.env.HONCHO_API_KEY || raw.hosts?.[HOST]?.apiKey || raw.apiKey;
if (!apiKey) return null;
return { apiKey, enabled: (raw.hosts?.codex?.enabled ?? raw.enabled) !== false };
}
function applyLocalConfig(base, localEnabled) {
if (!base) return null;
const effective = { ...base };
if (localEnabled !== undefined) effective.enabled = localEnabled;
if (!effective.apiKey) return base;
return effective;
}
for (const [description, [base, localEnabled]] of Object.entries({
"global empty default": [{apiKey:"k",peerName:"u"}, undefined],
"global explicit enabled=false": [{apiKey:"k",peerName:"u", enabled:false}, undefined],
"global hosts.codex enabled=false": [{apiKey:"k",peerName:"u", hosts: {codex: {enabled:false}}}, undefined],
"local overlay enabled=false": [{apiKey:"k",peerName:"u"}, false],
})) {
const cfg = applyLocalConfig(resolveFileConfig(base), localEnabled);
console.log(JSON.stringify({description, config: cfg}));
}
JSRepository: plastic-labs/codex-honcho
Length of output: 4762
Gate install on Config.enabled.
enabled can be explicitly false at the global root, hosts.codex, or a repo-local overlay; loadConfig() can still return that config if an API key is present. runMcpServer uses config.enabled before starting, so install can register a server that cannot start. Store the loaded config and install only when config?.enabled is 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 `@bin/codex-honcho.ts` around lines 115 - 117, Update the configuration flow
around loadConfig and installMcpServer: store the loaded config, then install
the MCP server only when config?.enabled is true. Preserve the existing
mcpInvoke(entry) registration and success message for enabled configurations,
while skipping installation when enabled is false or no config is loaded.
| function applyLocalConfig(base: Config | null, local: LocalConfigInfo | null): Config | null { | ||
| if (!local) return base; | ||
|
|
||
| let raw: FileConfig; | ||
| try { | ||
| raw = JSON.parse(readFileSync(local.path, "utf-8")) as FileConfig; | ||
| } catch { | ||
| return base; | ||
| } | ||
|
|
||
| // A local file normally inherits credentials and identity from the shared | ||
| // config, but it can also be fully self-contained when no global config is | ||
| // available. | ||
| const effective = base ? { ...base } : resolveFileConfig(raw); | ||
| if (!effective) return base; | ||
|
|
||
| for (const key of LOCAL_FIELDS) { | ||
| const value = localField(raw, key); | ||
| if (value !== undefined) (effective as unknown as Record<string, unknown>)[key] = value; | ||
| } | ||
| // Preserve the existing explicit environment override above both config | ||
| // files; a repo-local apiKey still works when HONCHO_API_KEY is absent. | ||
| if (process.env.HONCHO_API_KEY) effective.apiKey = process.env.HONCHO_API_KEY; | ||
| if (!effective.apiKey) return base; | ||
| effective.localConfig = local; | ||
| return effective; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Restrict inherited credentials when a repo-local file overrides endpoint.
applyLocalConfig copies endpoint from a file that lives inside the repository. The apiKey is inherited from the global config in that same object. A cloned repository can therefore ship .honcho/config.json with hosts.codex.endpoint.baseUrl set to an attacker-controlled host. The next hook run in that directory sends the user's global Honcho API key and the captured transcript data to that host through honchoClientOptions. Discovery is automatic, so no user action confirms the overlay.
Suggested mitigations, in order of preference:
- Ignore a local
endpointunless the local file also supplies its ownapiKey. - Restrict a local
endpoint.baseUrlto loopback hosts, and require the global file to opt in for anything else. - Require an explicit trust record (for example a list of approved local config paths in the global file) before any local overlay applies.
🔒 Minimal guard: do not pair an inherited key with a local endpoint
for (const key of LOCAL_FIELDS) {
const value = localField(raw, key);
if (value !== undefined) (effective as unknown as Record<string, unknown>)[key] = value;
}
+ // A local endpoint must not receive credentials inherited from ~/.honcho/config.json.
+ const localApiKey = localField(raw, "apiKey");
+ if (localField(raw, "endpoint") !== undefined && !localApiKey) {
+ effective.endpoint = base?.endpoint;
+ }
// Preserve the existing explicit environment override above both config
// files; a repo-local apiKey still works when HONCHO_API_KEY is absent.
if (process.env.HONCHO_API_KEY) effective.apiKey = process.env.HONCHO_API_KEY;📝 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 applyLocalConfig(base: Config | null, local: LocalConfigInfo | null): Config | null { | |
| if (!local) return base; | |
| let raw: FileConfig; | |
| try { | |
| raw = JSON.parse(readFileSync(local.path, "utf-8")) as FileConfig; | |
| } catch { | |
| return base; | |
| } | |
| // A local file normally inherits credentials and identity from the shared | |
| // config, but it can also be fully self-contained when no global config is | |
| // available. | |
| const effective = base ? { ...base } : resolveFileConfig(raw); | |
| if (!effective) return base; | |
| for (const key of LOCAL_FIELDS) { | |
| const value = localField(raw, key); | |
| if (value !== undefined) (effective as unknown as Record<string, unknown>)[key] = value; | |
| } | |
| // Preserve the existing explicit environment override above both config | |
| // files; a repo-local apiKey still works when HONCHO_API_KEY is absent. | |
| if (process.env.HONCHO_API_KEY) effective.apiKey = process.env.HONCHO_API_KEY; | |
| if (!effective.apiKey) return base; | |
| effective.localConfig = local; | |
| return effective; | |
| } | |
| function applyLocalConfig(base: Config | null, local: LocalConfigInfo | null): Config | null { | |
| if (!local) return base; | |
| let raw: FileConfig; | |
| try { | |
| raw = JSON.parse(readFileSync(local.path, "utf-8")) as FileConfig; | |
| } catch { | |
| return base; | |
| } | |
| // A local file normally inherits credentials and identity from the shared | |
| // config, but it can also be fully self-contained when no global config is | |
| // available. | |
| const effective = base ? { ...base } : resolveFileConfig(raw); | |
| if (!effective) return base; | |
| for (const key of LOCAL_FIELDS) { | |
| const value = localField(raw, key); | |
| if (value !== undefined) (effective as unknown as Record<string, unknown>)[key] = value; | |
| } | |
| // A local endpoint must not receive credentials inherited from ~/.honcho/config.json. | |
| const localApiKey = localField(raw, "apiKey"); | |
| if (localField(raw, "endpoint") !== undefined && !localApiKey) { | |
| effective.endpoint = base?.endpoint; | |
| } | |
| // Preserve the existing explicit environment override above both config | |
| // files; a repo-local apiKey still works when HONCHO_API_KEY is absent. | |
| if (process.env.HONCHO_API_KEY) effective.apiKey = process.env.HONCHO_API_KEY; | |
| if (!effective.apiKey) return base; | |
| effective.localConfig = local; | |
| return effective; | |
| } |
🤖 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/config.ts` around lines 165 - 191, Update applyLocalConfig so a
repository-local endpoint override is applied only when the local configuration
also provides its own apiKey; otherwise preserve the inherited endpoint while
allowing other local fields to merge. Ensure the existing environment-key
override and effective.apiKey validation remain intact, and prevent
localField(raw, "endpoint") from replacing the trusted endpoint when the key
would be inherited from base.
| for (const key of LOCAL_FIELDS) { | ||
| const value = localField(raw, key); | ||
| if (value !== undefined) (effective as unknown as Record<string, unknown>)[key] = value; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep run --pattern 'function resolvePeerName($$$) { $$$ }' --lang typescript src/config.ts
rg -n 'resolvePeerName' --type=tsRepository: plastic-labs/codex-honcho
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- files ---\n'
git ls-files | rg '(^|/)src/config\.ts$|package\.json|tsconfig\.json' || true
printf '\n--- outline ---\n'
ast-grep outline src/config.ts --view compact || true
printf '\n--- relevant symbols ---\n'
rg -n 'resolvePeerName|sessionName|LOCAL_FIELDS|localField|peerName|sessionStrategy|enabled' src/config.ts || true
printf '\n--- src/config.ts relevant sections ---\n'
sed -n '1,140p' src/config.ts
printf '\n--- 150-230 ---\n'
sed -n '150,230p' src/config.ts
printf '\n--- 230-340 ---\n'
sed -n '230,340p' src/config.tsRepository: plastic-labs/codex-honcho
Length of output: 254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- files ---'
git ls-files | rg '(^|/)src/config\.ts$|package\.json|tsconfig\.json' || true
printf '%s\n' ''
printf '%s\n' '--- outline ---'
ast-grep outline src/config.ts --view compact || true
printf '%s\n' ''
printf '%s\n' '--- relevant symbols ---'
rg -n 'resolvePeerName|sessionName|LOCAL_FIELDS|localField|peerName|sessionStrategy|enabled' src/config.ts || true
printf '%s\n' ''
printf '%s\n' '--- src/config.ts relevant sections ---'
sed -n '1,140p' src/config.ts
printf '%s\n' ''
printf '%s\n' '--- 150-230 ---'
sed -n '150,230p' src/config.ts
printf '%s\n' ''
printf '%s\n' '--- 230-340 ---'
sed -n '230,340p' src/config.tsRepository: plastic-labs/codex-honcho
Length of output: 13811
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node - <<'JS'
const enabledTruthinessChecks = [undefined, false, "false", 0, null, "", true, "true", 1, {}];
for (const value of enabledTruthinessChecks) {
const result = value !== false;
console.log(JSON.stringify(value) + " -> enabled: " + Boolean(result) + " (value !== false is true)");
}
const strategies = ["per-directory", "per-repo", "git-branch", "chat-instance", "bad-strategy", null, true, 123, {}];
for (const strategy of strategies) {
let name;
const directoryAnchor = "/tmp/repo";
const anchor = strategy === "per-repo" ? directoryAnchor : directoryAnchor;
const repo = strategy.toString().toLowerCase().replace(/[^a-z0-9-_]/g, "-");
try {
switch (strategy) {
case "git-branch":
name = repo;
break;
case "chat-instance":
name = repo;
break;
case "per-repo":
case "per-directory":
default:
name = repo;
}
} catch (e) {
name = `ERROR: ${e.message}`;
}
console.log(JSON.stringify(strategy) + " -> sessionName uses strategy branch `" + (strategy === "git-branch" ? "git-branch" : strategy === "chat-instance" ? "chat-instance" : "default") + "` -> `" + name + "`");
}
JSRepository: plastic-labs/codex-honcho
Length of output: 1728
Normalize and validate local config fields before applying the overlay.
applyLocalConfig writes raw JSON values through unknown, so invalid types can reach global fallback code. "enabled": "false" becomes enabled, "enabled": 0 becomes enabled, and an invalid sessionStrategy is accepted by the default branch. Local peerName also bypasses resolvePeerName. Coerce booleans, validate sessionStrategy against the allowed values before use, and normalize local peerName like the global file path does.
🤖 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/config.ts` around lines 181 - 184, Update applyLocalConfig’s LOCAL_FIELDS
overlay to normalize and validate each local value before assigning it: coerce
enabled-style fields using the existing boolean semantics, accept
sessionStrategy only when it matches an allowed value, and pass local peerName
through resolvePeerName just as the global configuration path does. Do not write
raw localField results through the unknown cast.
| export function memoryKey(config: Config, cwd: string, sessionId?: string): string { | ||
| return sessionId || sessionName(config, cwd, sessionId); | ||
| const key = sessionId || sessionName(config, cwd, sessionId); | ||
| // Keep queues/cursors/context from an existing global session out of a newly | ||
| // activated repo-local workspace. The default path remains byte-for-byte | ||
| // compatible with prior releases. | ||
| return config.localConfig ? `local-${config.workspace}-${key}` : key; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C3 'memoryKey\(' --type=tsRepository: plastic-labs/codex-honcho
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -e
echo "git status/files relevant:"
git ls-files | rg '(^src/config\.ts$|^bin/|config|package|README)' | sed -n '1,200p'
echo
echo "find config.ts and honcho:"
fd -a 'config\.ts$|codex-honcho\.ts$' . 2>/dev/null || true
echo
echo "search memoryKey text:"
rg -n -C3 'memoryKey|function memoryKey|const.*memoryKey' . 2>/dev/null || true
echo
echo "config.ts outline snippets around lines:"
if [ -f src/config.ts ]; then
wc -l src/config.ts
sed -n '1,380p' src/config.ts
fiRepository: plastic-labs/codex-honcho
Length of output: 19668
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re
config = Path("src/config.ts").read_text()
honcho = Path("bin/codex-honcho.ts").read_text()
fn = re.search(r'export function memoryKey\([\s\S]*?\n\}', config)
calls = []
for m in re.finditer(r'memoryKey\s*\((.+?)\)', honcho + "\n" + config):
calls.append(m.group(0))
fn_text = fn.group(0) if fn else "<missing>"
print("memoryKey implementation:")
print(fn_text)
print("\nmemoryKey call sites:")
for c in calls:
print(c)
def memory_key(workspace, session_name, local_root, session_id):
key = session_id or session_name
return f"local-{workspace}-{key}" if local_root else key
cases = [
{"name": "same local workspace/session_name, missing session_id, different roots",
"w1": "my-ws", "sn1": "my-project", "root1": "/repo-a",
"w2": "my-ws", "sn2": "my-project", "root2": "/repo-b", "session_id": None},
{"name": "same local workspace/session_name, same session_id, different roots",
"w1": "my-ws", "sn1": "my-project", "root1": "/repo-a",
"w2": "my-ws", "sn2": "my-project", "root2": "/repo-b", "session_id": "abc"},
{"name": "local workspace plus different session_name/session_id",
"w1": "my-ws", "sn1": "my-project", "root1": "/repo-a",
"w2": "my-ws", "sn2": "other-project", "root2": "/repo-b", "session_id": "xyz"},
]
for case in cases:
k1 = memory_key(case["w1"], case["sn1"], case["root1"], case["session_id"])
k2 = memory_key(case["w2"], case["sn2"], case["root2"], case["session_id"])
print(f'\n{case["name"]}')
print(f'key1={k1}')
print(f'key2={k2}')
print(f'collision={k1==k2}')
PYRepository: plastic-labs/codex-honcho
Length of output: 1098
Use another stable discriminator when the same local workspace is reused.
memoryKey uses only local-<workspace>-<key>, so distinct repositories with the same local workspace and missing session_id share queue, cursor, and context files. Include localConfig.root in the local key if repo-local state should remain isolated.
🤖 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/config.ts` around lines 319 - 325, Update memoryKey to include
localConfig.root in the local key alongside the workspace and key, ensuring
repositories sharing a workspace remain isolated when sessionId is absent.
Preserve the existing non-local key behavior and the current sessionName
fallback.
| const config = loadConfig(cwd); | ||
| if (!config || !config.enabled) throw new Error("Honcho is not configured for this workspace"); | ||
|
|
||
| const honcho = new Honcho(honchoClientOptions(config)); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
Prevent repository-local endpoints from receiving inherited credentials.
A cloned repository can supply .honcho/config.json with an attacker-controlled endpoint. The resolver can then combine that endpoint with HONCHO_API_KEY or the global API key. The SDK client sends the inherited credential to that endpoint when a hook or MCP tool makes a request.
src/mcp.ts#L150-L153: enforce credential provenance before constructingHoncho. Do not send a global or environment API key to an endpoint selected by repository-local configuration. Restrict local endpoint overrides or require an explicit trusted opt-in.README.md#L138-L151: remove the claim that local endpoint overrides safely inherit omitted credentials until the resolver enforces this boundary. Document the resulting trust rule.
📍 Affects 2 files
src/mcp.ts#L150-L153(this comment)README.md#L138-L151
🤖 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/mcp.ts` around lines 150 - 153, The Honcho construction in src/mcp.ts
lines 150-153 must prevent repository-local endpoint configuration from
inheriting global or environment credentials: restrict local endpoint overrides
or require an explicit trusted opt-in before passing credentials to Honcho,
while preserving normal trusted configurations. Update README.md lines 138-151
to remove the unsafe inheritance claim and document the enforced endpoint and
credential trust rule.
|
This PR directly addresses an important self-hosted Honcho compatibility gap. The local stdio MCP approach is the right direction, and I support merging it. |
Summary
Adds an optional repository-local
.honcho/config.jsonoverlay so each project can route Codex memory to its own Honcho workspace while inheriting credentials and defaults from the global~/.honcho/config.json.This ports the approach from plastic-labs/claude-honcho#44 and adds the Hermes-compatible
per-reposession strategy.Why
The Codex integration currently resolves Honcho configuration globally. When working across multiple repositories, messages, tool activity, and derived conclusions all land in the same workspace, allowing unrelated project context to bleed together.
The existing hosted MCP registration also cannot follow the active working directory: its credentials and routing are fixed in Codex configuration at install time. A local overlay would therefore affect hooks but not MCP recall tools unless MCP configuration resolution moved into a cwd-aware local process.
How it works
.honcho/config.jsonat or above the working directory is layered over the global config. Omitted values continue to inherit globally, so a repository file can usually contain only{ "workspace": "my-project" }.hosts.codexare supported, with environment-provided API keys retaining highest precedence..honcho/, preventing subdirectories from unintentionally creating separate sessions.per-repostrategy. All directories in the same Git repository share the Git root’s session name. Outside Git it falls back toper-directory; worktrees and submodules with.gitfiles are supported.sessionNamepins an exact session, whilesplitSubmoduleslets other strategies give nested repositories independent anchors.Compatibility and security
The feature is additive and opt-in. Without a repository-local
.honcho/config.json, existing global configuration and session behavior remain unchanged.API keys no longer need to appear in Codex’s MCP configuration. Repository files should contain only non-secret routing settings;
.envfiles are explicitly ignored.Verification
bun test— 92 tests, 207 expectationsbun run typecheckbun run buildnpm pack --dry-runSummary by CodeRabbit
New Features
Documentation
Chores