Skip to content

Multi Project Mode

Paul edited this page Sep 9, 2026 · 7 revisions

Multi-Project Mode

By default the server is pinned to one project: --work-dir is the project root, and every tool resolves against it. Multi-project mode turns --work-dir into an access root — a directory beneath which each conversation selects its own project — so one running server can serve many repositories without a process per repo.

Enable it with --multi-project or "multiProject": true.

codexify --work-dir /path/to/projects --multi-project

One static codexify.config.json is still read once at startup; selection changes only the effective work directory the project tools use, never the server configuration itself.


How a conversation binds

Each conversation binds a workspace exactly once through set_project_root. An ordinary new binding selects either a project (path) or a private scratch workspace (withoutProject: true); a continuation binding instead reuses an exact previously recorded workspace (resumePath). When the intended new project isn't known, list_projects provides a search step first. The setup app presents the ordinary choices as a searchable picker, with Chat without a project first and a worktree checkbox before the project results.

  • Ordinary path rules. path is relative to the access root or absolute, but its canonical target must be an existing directory inside that root. Traversal (..) and symlink escapes are rejected after canonicalization, so a link pointing outside can't smuggle a selection past the check.
  • Git repository URLs. path may instead be a provider-agnostic HTTPS or SSH clone URL ending in .git, including GitLab and SCP-style SSH forms such as git@gitlab.com:group/repository.git. GitHub additionally accepts repository-root URLs without .git plus HTTPS branch (/tree/<branch>), pull-request (/pull/<number>), and full-40-hex commit (/commit/<sha>) URLs. Codexify reuses exactly one matching local checkout when possible, otherwise it clones into projectCloneDir / --project-clone-dir (default: the access root). Provider-agnostic URLs select the repository's default checkout. Targeted GitHub URLs fetch the exact branch/PR/commit; when an existing source checkout is on another commit, Codexify leaves it untouched and normally binds a detached managed worktree at the requested target. If createWorktree: false or mode never forbids that worktree, selection succeeds only when the source checkout is already at the exact target. Credential-bearing HTTPS URLs, query strings/fragments, file://, plain HTTP, git://, and other arbitrary transports are rejected.
  • Binding scope. The binding belongs to the ChatGPT conversation, keyed from _meta["openai/session"] (hashed, never stored raw). Simultaneous chats can hold different projects, and a later turn recovers its own root after MCP reconnects or a server restart. A client with no ChatGPT conversation metadata falls back to a binding that lasts only the current MCP transport session.
  • Per-selection worktree override. A path request may add createWorktree: true to force a managed worktree or false to force the source checkout. Omitting it applies worktrees.mode. The override affects only that binding and never rewrites configuration. Repeating an immutable binding with a conflicting placement request is rejected.
  • No switching. A conversation cannot switch roots once bound — start another chat for another project. Re-selecting the same canonical workspace and placement is idempotent.
  • Before selection. Project-scoped tools are unavailable and say why. list_projects and set_project_root are the two project-independent tools present for this workflow.

Chat without a project

Pass withoutProject: true to set_project_root, or choose Chat without a project in the setup app, when the task needs files and commands but no repository. This choice is mutually exclusive with path.

  • A ChatGPT conversation receives a durable private scratch directory outside the configured project access root. Its files and binding survive MCP reconnects and Codexify restarts.
  • A generic MCP client without stable ChatGPT conversation metadata receives a transport-scoped scratch directory that is removed when that transport session ends.
  • Filesystem, command, memory, skill, and project-instruction tools resolve against the scratch directory. The project catalogue and configured access-root contents are not exposed through it.
  • The scratch choice is immutable, just like a project binding. Start a new chat to choose a project later.

After either choice, call get_agent_brief before using workspace-scoped tools. set_project_root reports the active scratch path, or both the active managed worktree and source checkout when they differ.

Resume an existing workspace in a new conversation

resumePath is reserved for a continuation prompt produced from an older-schema ChatGPT conversation. It must be the exact absolute active path of a workspace already present in Codexify's saved bindings under the same access root, and it cannot be combined with path, withoutProject, or createWorktree:

{ "resumePath": "/absolute/path/to/the/existing/workspace" }

Call it before any ordinary project selection. Codexify canonicalizes the path, locks the destination conversation's binding, and accepts only one of these previously validated targets:

  • the exact project root from an existing project binding, retaining whether it is a direct checkout or managed worktree and its source/worktree metadata; or
  • the exact persistent scratch root from an existing ChatGPT scratch marker in the same access-root namespace.

This is intentionally not ordinary access-root selection. A recorded managed worktree or scratch root may live outside the access root, but an arbitrary outside path cannot be resumed. Resumption never clones, fetches, pulls, creates a worktree, runs a worktree setup script, resets, or switches branches. Relative, unrecorded, missing, deleted, wrong-scope, malformed, or tampered targets fail without binding anything; the caller must not fall back to path or allocate a replacement workspace. Transport-scoped clients cannot resume because they lack stable ChatGPT conversation metadata.

Resumption preserves the filesystem and project-root-keyed memory exactly, including staged, unstaged, and untracked changes. It does not transfer conversation messages, in-memory command sessions, or conversation-keyed diff checkpoints. The original binding remains valid, so the two conversations must not edit the shared workspace concurrently. Repeating the same resume in the destination conversation is idempotent; trying to resume a different workspace into an already-bound conversation is rejected.

The project catalogue

list_projects searches a read-only catalogue assembled from two sources:

  1. Native Codex trust table — Codex records trust decisions in its user config:

    [projects."/absolute/path/to/project"]
    trust_level = "trusted"

    Codexify reads those paths as candidates. It doesn't treat the table as exhaustive — entries may be stale or lack descriptions.

  2. Explicit projectCatalog.entries — operator-authored providers that can add aliases/descriptions or supply projects absent from the native table:

    {
      "multiProject": true,
      "projectCatalog": {
        "codexConfig": { "enabled": true, "trustedOnly": true },
        "entries": [
          {
            "path": "codexify",
            "name": "Codexify",
            "aliases": ["ChatGPT MCP bridge"],
            "description": "Rust MCP bridge exposing local programming tools to ChatGPT"
          }
        ]
      }
    }

Every candidate still passes Codexify's own checks: it must exist, resolve to a directory, and canonicalize to the access root itself or a descendant. Missing entries, files, and symlink escapes are skipped; duplicate canonical targets are merged. Native Codex trust is only catalogue metadata plus the default trustedOnly filter — it never grants access outside --work-dir, and neither does an explicit entry.

list_projects returns a selector relative to the access root, passable unchanged as set_project_root.path. Its optional query matches names, aliases, descriptions, and selectors case-insensitively with exact/prefix/substring ranking. It never binds automatically — if several results are plausible, the agent's instructions require it to ask you rather than guess, because a wrong binding can't be changed in that conversation.

The native table is read live on every list_projects call, so newly trusted projects become discoverable without restarting. Project-local .codex/config.toml layers are not scanned — they matter only after selection. Catalogue construction never opens a candidate's README, source, .codex/, or AGENTS.md; contents stay unread until the conversation selects that project.

Git URL selection is separate from catalogue listing. Before cloning, Codexify checks the conventional destination, catalogue candidates, and immediate children of projectCloneDir, comparing normalized remotes at each Git top level. Exactly one match is reused; multiple matches are rejected as ambiguous. Non-GitHub URLs must end in .git unless a matching checkout already exists. GitHub branch names may contain /; everything after /tree/ is treated as the branch ref.

Per-conversation isolation

  • Worktrees. With the default worktree mode, the first ordinary conversation selecting a Git project uses the source checkout directly; a second ordinary selection gets a detached managed worktree. Explicit createWorktree overrides this placement. A resumePath continuation deliberately reuses the recorded tree and therefore must not run concurrently with its source conversation. See Worktree Isolation.
  • Scratch workspaces. Projectless chats do not create or reuse Git worktrees. Their private scratch roots have the conversation or transport lifetime described above.
  • Saved state. With an explicit memory.dir, each selected project gets its own hashed child directory. Conversation bindings stay enabled even when memory.enabled is false. See Context and Memory.
  • Diff checkpoints. Namespaced per conversation/project pair. See Diff Checkpoints.

Managing bindings

To clear a stray binding, delete its file under ~/.codexify/conversation-projects/. There is no tool to re-point an already-bound conversation; resumePath only creates a new conversation binding to an exact workspace already authorized by another saved binding. A managed worktree stays referenced while any binding exists; startup cleanup skips referenced or dirty worktrees and only removes older clean, unreferenced entries beyond keepCount.

Inspecting the catalogue without starting a server

codexify projects list --work-dir /path/to/projects
codexify projects list --work-dir /path/to/projects --query "codexify"
codexify projects list --work-dir /path/to/projects --json
codexify projects list --work-dir /path/to/projects --show-skipped

--show-skipped (local-only) also prints paths rejected as missing, untrusted, or outside the access root, plus merged duplicates. See CLI Reference.


Security note

Multi-project selection isolates Codexify's structured tools and per-conversation state — it is not an OS sandbox. The entire access-root subtree is intentionally selectable by a new conversation, and resumePath can alias an exact previously authorized managed worktree or scratch path outside it. Treat every reachable workspace as sensitive. See Security Model.


See also

Clone this wiki locally