Repository navigation
Fix Codex native titles not reaching terminal tabs #12062
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
19cbfb1
test: cover Codex native title tab sync
austinywang be25860
fix: sync Codex native titles to terminal tabs
austinywang 7dee65c
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang 9cd72d4
fix: harden Codex native title sync
austinywang eaf9356
fix: expose Codex title handler to async bridge
austinywang a848541
fix: capture title sync bridge explicitly
austinywang e82c6c9
fix: retry Codex title reads during SQLite contention
austinywang 98419dd
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang 2c6f02f
test: cover Codex visible tab header reconciliation
austinywang 436ec7c
fix: reconcile stale Codex tab headers
austinywang 8dcce72
fix: use SQLite busy timeout for native title lookup
austinywang c99c2dd
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang ff9d783
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang 75f16f0
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang 2c18ee6
Merge branch 'main' into issue-11144-codex-tab-title
austinywang 2408b69
Merge branch 'main' into issue-11144-codex-tab-title
austinywang da820c5
Merge branch 'main' into issue-11144-codex-tab-title
austinywang dde38c4
Merge branch 'main' into issue-11144-codex-tab-title
austinywang dca65a1
Merge branch 'main' into issue-11144-codex-tab-title
austinywang ed6a324
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang 5101355
fix: compile async title socket test
austinywang f7ff531
Merge remote-tracking branch 'origin/main' into issue-11144-codex-tab…
austinywang fb92dc3
fix: refresh window title for native Codex sync
austinywang dd47542
fix: reject stale Codex native title hooks
austinywang b12f3a9
fix: gate Codex title sync by surface owner
austinywang 23bee2f
test: keep CLI ledger coverage out of app target
austinywang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/CodexNativeTitleStore.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import Foundation | ||
| import SQLite3 | ||
|
|
||
| /// Reads Codex's authoritative native thread title from its state database. | ||
| public struct CodexNativeTitleStore: Sendable { | ||
| private let databasePath: String | ||
|
|
||
| /// Creates a title store for the Codex profile at `codexHome`. | ||
| public init(codexHome: String? = nil) { | ||
| self.databasePath = Self.databasePath(forCodexHome: codexHome) | ||
| } | ||
|
|
||
| /// Creates a title store for a database path. | ||
| /// | ||
| /// This initializer is internal so production callers select a Codex home, | ||
| /// while package tests can inject deterministic SQLite fixtures. | ||
| init(databasePath: String) { | ||
| self.databasePath = databasePath | ||
| } | ||
|
|
||
| /// Returns the non-empty title for an active session, or nil when Codex's | ||
| /// database, session row, or title is unavailable. | ||
| public func title(forSessionId sessionId: String) -> String? { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| let normalizedSessionId = sessionId.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard !normalizedSessionId.isEmpty else { return nil } | ||
|
|
||
| var database: OpaquePointer? | ||
| guard sqlite3_open_v2(databasePath, &database, SQLITE_OPEN_READONLY, nil) == SQLITE_OK, | ||
| let database else { | ||
| sqlite3_close(database) | ||
| return nil | ||
| } | ||
| defer { sqlite3_close(database) } | ||
| sqlite3_busy_timeout(database, 250) | ||
|
|
||
| var statement: OpaquePointer? | ||
| let query = "SELECT title FROM threads WHERE id = ?1 AND archived = 0 LIMIT 1" | ||
| guard sqlite3_prepare_v2(database, query, -1, &statement, nil) == SQLITE_OK, | ||
| let statement else { | ||
| sqlite3_finalize(statement) | ||
| return nil | ||
| } | ||
| defer { sqlite3_finalize(statement) } | ||
|
|
||
| let transient = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self) | ||
| sqlite3_bind_text(statement, 1, normalizedSessionId, -1, transient) | ||
| guard sqlite3_step(statement) == SQLITE_ROW, | ||
| let titleBytes = sqlite3_column_text(statement, 0) else { | ||
| return nil | ||
| } | ||
| let title = String(cString: titleBytes).trimmingCharacters(in: .whitespacesAndNewlines) | ||
| return title.isEmpty ? nil : title | ||
| } | ||
|
|
||
| /// Builds the state-database path for a Codex home directory. | ||
| private static func databasePath(forCodexHome codexHome: String?) -> String { | ||
| let home = (codexHome ?? "~/.codex") as NSString | ||
| return URL(fileURLWithPath: home.expandingTildeInPath, isDirectory: true) | ||
| .appendingPathComponent("state_5.sqlite", isDirectory: false) | ||
| .path | ||
| } | ||
| } | ||
70 changes: 70 additions & 0 deletions
70
Packages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/CodexNativeTitleStoreTests.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import Foundation | ||
| import SQLite3 | ||
| import Testing | ||
|
|
||
| @testable import CMUXAgentLaunch | ||
|
|
||
| struct CodexNativeTitleStoreTests { | ||
| private enum FixtureError: Error { | ||
| case database | ||
| } | ||
|
|
||
| /// Reads the title associated with the requested Codex session only. | ||
| @Test func readsTheNativeThreadTitleForAnExactSession() throws { | ||
| let fixture = try makeDatabase(sessionId: "codex-session", title: "測試 cmux 與 codex Tab 同步") | ||
| defer { try? FileManager.default.removeItem(at: fixture.directory) } | ||
|
|
||
| let store = CodexNativeTitleStore(databasePath: fixture.database.path) | ||
| #expect(store.title(forSessionId: fixture.sessionId) == "測試 cmux 與 codex Tab 同步") | ||
| } | ||
|
|
||
| /// Returns no title for missing, empty, or unavailable session data. | ||
| @Test(arguments: ["missing-session", ""]) | ||
| func returnsNilWhenTheTitleCannotBeResolved(sessionId: String) throws { | ||
| let fixture = try makeDatabase(sessionId: "codex-session", title: "existing title") | ||
| defer { try? FileManager.default.removeItem(at: fixture.directory) } | ||
|
|
||
| let store = CodexNativeTitleStore(databasePath: fixture.database.path) | ||
| #expect(store.title(forSessionId: sessionId) == nil) | ||
| let missingDatabase = fixture.directory.appendingPathComponent("missing.sqlite") | ||
| let missingStore = CodexNativeTitleStore(databasePath: missingDatabase.path) | ||
| #expect(missingStore.title(forSessionId: sessionId) == nil) | ||
| } | ||
|
|
||
| /// Creates a temporary SQLite database that mirrors the Codex thread table. | ||
| private func makeDatabase(sessionId: String, title: String) throws -> (directory: URL, database: URL, sessionId: String) { | ||
| let directory = FileManager.default.temporaryDirectory | ||
| .appendingPathComponent("cmux-codex-title-test-\(UUID().uuidString)", isDirectory: true) | ||
| try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) | ||
| let database = directory.appendingPathComponent("state_5.sqlite") | ||
|
|
||
| var connection: OpaquePointer? | ||
| guard sqlite3_open(database.path, &connection) == SQLITE_OK, let connection else { | ||
| throw FixtureError.database | ||
| } | ||
| defer { sqlite3_close(connection) } | ||
|
|
||
| let schema = """ | ||
| CREATE TABLE threads ( | ||
| id TEXT PRIMARY KEY, | ||
| title TEXT NOT NULL, | ||
| archived INTEGER NOT NULL DEFAULT 0 | ||
| ) | ||
| """ | ||
| guard sqlite3_exec(connection, schema, nil, nil, nil) == SQLITE_OK else { | ||
| throw FixtureError.database | ||
| } | ||
|
|
||
| var statement: OpaquePointer? | ||
| guard sqlite3_prepare_v2(connection, "INSERT INTO threads (id, title) VALUES (?1, ?2)", -1, &statement, nil) == SQLITE_OK, | ||
| let statement else { | ||
| throw FixtureError.database | ||
| } | ||
| defer { sqlite3_finalize(statement) } | ||
| let transient = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self) | ||
| sqlite3_bind_text(statement, 1, sessionId, -1, transient) | ||
| sqlite3_bind_text(statement, 2, title, -1, transient) | ||
| guard sqlite3_step(statement) == SQLITE_DONE else { throw FixtureError.database } | ||
| return (directory, database, sessionId) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.