Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions bridges/codex/backfill.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
TurnID string `json:"turn_id"`
}

func (cc *CodexClient) syncStoredCodexThreads(ctx context.Context) error {

Check failure on line 101 in bridges/codex/backfill.go

View workflow job for this annotation

GitHub Actions / Lint

func (*CodexClient).syncStoredCodexThreads is unused (U1000)

Check failure on line 101 in bridges/codex/backfill.go

View workflow job for this annotation

GitHub Actions / Lint

func (*CodexClient).syncStoredCodexThreads is unused (U1000)
if cc == nil || cc.UserLogin == nil || cc.UserLogin.Bridge == nil {
return nil
}
Expand Down Expand Up @@ -128,23 +128,25 @@
if cwd == "" {
return 0, 0, nil
}
threads, err := cc.listCodexThreads(ctx, cwd)
threads, err := cc.listCodexThreads(ctx, "")
if err != nil {
return 0, 0, err
}
if len(threads) == 0 {
return 0, 0, nil
}
portalsByThreadID, err := cc.existingCodexPortalsByThreadID(ctx)
if err != nil {
return 0, 0, err
}
createdCount := 0
total := 0
for _, thread := range threads {
threadID := strings.TrimSpace(thread.ID)
if threadID == "" {
continue
}
if !workspaceContains(cwd, thread.Cwd) {
continue
}
total++
portal, created, err := cc.ensureCodexThreadPortal(ctx, portalsByThreadID[threadID], thread)
if err != nil {
cc.log.Warn().Err(err).Str("thread_id", threadID).Str("cwd", cwd).Msg("Failed to sync Codex thread portal")
Expand All @@ -155,7 +157,7 @@
createdCount++
}
}
return len(threads), createdCount, nil
return total, createdCount, nil
}

func (cc *CodexClient) existingCodexPortalsByThreadID(ctx context.Context) (map[string]*bridgev2.Portal, error) {
Expand Down Expand Up @@ -218,12 +220,13 @@
}
meta := portalMeta(portal)
meta.IsCodexRoom = true
meta.PortalKind = codexPortalKindChat
meta.CodexThreadID = threadID
meta.ManagedImport = true
if cwd := strings.TrimSpace(thread.Cwd); cwd != "" {
meta.CodexCwd = cwd
}
meta.AwaitingCwdSetup = strings.TrimSpace(meta.CodexCwd) == ""
meta.WorkspaceRoot = cc.workspaceRootForCwd(meta.CodexCwd)

title := codexThreadTitle(thread)
if title == "" {
Expand Down Expand Up @@ -252,16 +255,16 @@
return nil, false, err
}
if created {
if meta.AwaitingCwdSetup {
cc.sendSystemNotice(ctx, portal, "This imported conversation needs a working directory. Send an absolute path or `~/...`.")
}
} else {
cc.UserLogin.Bridge.WakeupBackfillQueue()
}
if err := portal.Save(ctx); err != nil {
return nil, false, err
}
cc.syncCodexRoomTopic(ctx, portal, meta)
if err := cc.attachChatToWorkspaceSpace(ctx, portal, meta.WorkspaceRoot); err != nil {
return nil, false, err
}

return portal, created, nil
}
Expand Down
27 changes: 13 additions & 14 deletions bridges/codex/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,9 @@ func (cc *CodexClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Ma
if res, handled, err := cc.handleCodexCommand(ctx, portal, meta, body); handled {
return res, err
}

if meta.AwaitingCwdSetup {
return cc.handleWelcomeCodexMessage(ctx, portal, meta, body)
if !isCodexChatPortal(meta) {
cc.sendSystemNotice(ctx, portal, "Use `!codex help` for Codex workspace commands.")
return &bridgev2.MatrixMessageResponse{Pending: false}, nil
}
Comment on lines +496 to 499
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 | 🟠 Major

Preserve legacy chat rooms until PortalKind is migrated.

Older Codex portals load with PortalKind == "", but isCodexChatPortal() now only accepts the explicit "chat" kind. That means pre-existing chat rooms will hit this branch, stop delivering normal messages, and also skip the delete path later in this file until some other flow rewrites their metadata.

💡 Backward-compatible fallback
+	legacyChatPortal := meta.PortalKind == "" &&
+		meta.IsCodexRoom &&
+		(strings.TrimSpace(meta.CodexThreadID) != "" || strings.TrimSpace(meta.CodexCwd) != "")
-	if !isCodexChatPortal(meta) {
+	if !isCodexChatPortal(meta) && !legacyChatPortal {
 		cc.sendSystemNotice(ctx, portal, "Use `!codex help` for Codex workspace commands.")
 		return &bridgev2.MatrixMessageResponse{Pending: false}, nil
 	}
📝 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.

Suggested change
if !isCodexChatPortal(meta) {
cc.sendSystemNotice(ctx, portal, "Use `!codex help` for Codex workspace commands.")
return &bridgev2.MatrixMessageResponse{Pending: false}, nil
}
legacyChatPortal := meta.PortalKind == "" &&
meta.IsCodexRoom &&
(strings.TrimSpace(meta.CodexThreadID) != "" || strings.TrimSpace(meta.CodexCwd) != "")
if !isCodexChatPortal(meta) && !legacyChatPortal {
cc.sendSystemNotice(ctx, portal, "Use `!codex help` for Codex workspace commands.")
return &bridgev2.MatrixMessageResponse{Pending: false}, nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bridges/codex/client.go` around lines 496 - 499, The branch rejects legacy
Codex portals because isCodexChatPortal(meta) only returns true for PortalKind
== "chat"; update the check to accept legacy empty PortalKind as chat until
migration completes. Change the condition in the handler (where
isCodexChatPortal(meta) is used) to treat meta.PortalKind == "" as a chat portal
(e.g., replace isCodexChatPortal(meta) with a predicate that returns true when
meta.PortalKind == "chat" OR meta.PortalKind == ""), or adjust isCodexChatPortal
to consider empty PortalKind a legacy chat; keep existing calls to
cc.sendSystemNotice(ctx, portal, ...) and return the same MatrixMessageResponse
shape.


if err := cc.ensureRPC(cc.backgroundContext(ctx)); err != nil {
Expand Down Expand Up @@ -1502,12 +1502,12 @@ func (cc *CodexClient) backgroundContext(ctx context.Context) context.Context {
func (cc *CodexClient) bootstrap(ctx context.Context) {
cc.waitForLoginPersisted(ctx)
syncSucceeded := true
if err := cc.ensureWelcomeCodexChat(cc.backgroundContext(ctx)); err != nil {
cc.log.Warn().Err(err).Msg("Failed to ensure default Codex chat during bootstrap")
if err := cc.reconcileTrackedWorkspacesFromConfig(cc.backgroundContext(ctx)); err != nil {
cc.log.Warn().Err(err).Msg("Failed to reconcile tracked Codex workspaces from config")
syncSucceeded = false
}
if err := cc.syncStoredCodexThreads(cc.backgroundContext(ctx)); err != nil {
cc.log.Warn().Err(err).Msg("Failed to sync Codex threads during bootstrap")
if err := cc.ensureWelcomeCodexChat(cc.backgroundContext(ctx)); err != nil {
cc.log.Warn().Err(err).Msg("Failed to ensure default Codex chat during bootstrap")
syncSucceeded = false
}
meta := loginMetadata(cc.UserLogin)
Expand Down Expand Up @@ -1619,6 +1619,7 @@ func (cc *CodexClient) ensureCodexThread(ctx context.Context, portal *bridgev2.P
if meta == nil || portal == nil {
return errors.New("missing portal/meta")
}
meta.PortalKind = codexPortalKindChat
if strings.TrimSpace(meta.CodexCwd) == "" {
return errors.New("codex working directory not set")
}
Expand Down Expand Up @@ -1656,6 +1657,7 @@ func (cc *CodexClient) ensureCodexThread(ctx context.Context, portal *bridgev2.P
if meta.CodexThreadID == "" {
return errors.New("codex returned empty thread id")
}
meta.WorkspaceRoot = cc.workspaceRootForCwd(meta.CodexCwd)
if err := portal.Save(ctx); err != nil {
return err
}
Expand All @@ -1664,7 +1666,7 @@ func (cc *CodexClient) ensureCodexThread(ctx context.Context, portal *bridgev2.P
cc.loadedMu.Unlock()
cc.restoreRecoveredActiveTurns(portal, meta, resp.Thread, resp.Model)
cc.syncCodexRoomTopic(ctx, portal, meta)
return nil
return cc.attachChatToWorkspaceSpace(ctx, portal, meta.WorkspaceRoot)
}

func (cc *CodexClient) ensureCodexThreadLoaded(ctx context.Context, portal *bridgev2.Portal, meta *PortalMetadata) error {
Expand Down Expand Up @@ -1706,7 +1708,8 @@ func (cc *CodexClient) ensureCodexThreadLoaded(ctx context.Context, portal *brid
cc.loadedMu.Unlock()
cc.restoreRecoveredActiveTurns(portal, meta, resp.Thread, resp.Model)
cc.syncCodexRoomTopic(ctx, portal, meta)
return nil
meta.WorkspaceRoot = cc.workspaceRootForCwd(meta.CodexCwd)
return cc.attachChatToWorkspaceSpace(ctx, portal, meta.WorkspaceRoot)
}

// HandleMatrixDeleteChat best-effort archives the Codex thread and removes the temp cwd.
Expand All @@ -1719,11 +1722,7 @@ func (cc *CodexClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2
if meta == nil || !meta.IsCodexRoom {
return nil
}
if meta.AwaitingCwdSetup {
go func() {
time.Sleep(1 * time.Second)
_ = cc.ensureWelcomeCodexChat(cc.backgroundContext(ctx))
}()
if !isCodexChatPortal(meta) {
return nil
}
if err := cc.ensureRPC(ctx); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions bridges/codex/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type CodexConfig struct {
Command string `yaml:"command"`
Listen string `yaml:"listen"`
HomeBaseDir string `yaml:"home_base_dir"`
TrackedPaths []string `yaml:"tracked_paths"`
DefaultModel string `yaml:"default_model"`
NetworkAccess *bool `yaml:"network_access"`
ClientInfo *CodexClientInfo `yaml:"client_info"`
Expand All @@ -40,6 +41,7 @@ codex:
enabled: true
command: "codex"
listen: ""
tracked_paths: []
default_model: "gpt-5.1-codex"
network_access: true
client_info:
Expand Down
1 change: 1 addition & 0 deletions bridges/codex/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ func (cc *CodexConnector) applyRuntimeDefaults() {
if strings.TrimSpace(cc.Config.Codex.DefaultModel) == "" {
cc.Config.Codex.DefaultModel = "gpt-5.1-codex"
}
cc.Config.Codex.TrackedPaths = normalizeTrackedWorkspaceRoots(cc.Config.Codex.TrackedPaths)
bridgesdk.ApplyBoolDefault(&cc.Config.Codex.NetworkAccess, true)
if cc.Config.Codex.ClientInfo == nil {
cc.Config.Codex.ClientInfo = &CodexClientInfo{}
Expand Down
1 change: 1 addition & 0 deletions bridges/codex/constructors.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func NewConnector() *CodexConnector {
dbutil.ZeroLogger(bridge.Log.With().Str("db_section", "codex_bridge").Logger()),
)
}
cc.initProvisioning()
},
StartConnector: func(ctx context.Context, _ *bridgev2.Bridge) error {
db := cc.bridgeDB()
Expand Down
Loading
Loading