From 07656997058c056315d2810c2916bcb2fd7b65db Mon Sep 17 00:00:00 2001 From: Osamaali313 Date: Mon, 31 Aug 2026 22:08:30 +0300 Subject: [PATCH] Fix Codex notify being scoped into the last TOML table CodexInstaller.install appended the notify block at end-of-file. In TOML a bare key written after a [table] header belongs to that table, so for any config whose last section is a table -- very common once there's an [mcp_servers.*] block, or [tui]/[history] -- the appended key became mcp_servers..notify instead of a document-root key. Codex only reads a top-level notify, so the relay was silently never invoked and the whole Codex integration did nothing. install() still reported success and wrote a backup, so nothing signalled the failure. Insert the block above the first table header instead, falling back to an end-of-file append when the file has no tables. The placement is pulled into a pure insertingAtRoot helper and covered by a test (the existing suite tests only the pure logic, never the home-directory I/O paths). --- app/Roster/Data/ProviderInstallers.swift | 24 ++++++++++++-- app/RosterTests/ProviderInstallersTests.swift | 32 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/Roster/Data/ProviderInstallers.swift b/app/Roster/Data/ProviderInstallers.swift index 68fd493..0770a44 100644 --- a/app/Roster/Data/ProviderInstallers.swift +++ b/app/Roster/Data/ProviderInstallers.swift @@ -324,6 +324,27 @@ enum CodexInstaller { } } + /// Places `block` as a document-root TOML entry. A bare key written after a + /// `[table]` header belongs to that table, so appending at end-of-file would + /// scope our `notify` into the file's last table (commonly an + /// `[mcp_servers.*]` block) and Codex would never see it. The block is + /// inserted just above the first table header instead; a file with no tables + /// (or an empty file) simply gets the block appended. + static func insertingAtRoot(_ block: String, into text: String) -> String { + if text.isEmpty { return block + "\n" } + let lines = text.components(separatedBy: "\n") + guard let idx = lines.firstIndex(where: { + $0.trimmingCharacters(in: .whitespaces).hasPrefix("[") + }) else { + return text + "\n\n" + block + "\n" + } + var head = Array(lines[..