Skip to content

[QOL]: Treat directories with .git or opencode.json as local projects (not global) #15192

Closed as not planned

Description

@diegohb

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Description:

OpenCode currently only promotes a directory from “global” to a distinct project when Git has at least one commit. This forces users to make an initial commit even when they only want project-scoped config or agents. We should treat a directory as a local project if either (a) a .git directory exists (even without commits) or (b) an opencode.json file is present at the root.

Current behavior

  • git init alone (no commits) → project.id === "global" 1
  • Adding opencode.json does not affect project ID detection 2
  • Tests confirm: repo with only git init yields global; repo with commits yields non-global 3

Expected behavior

  • Presence of .git directory OR opencode.json should cause OpenCode to assign a non-global project ID and enable project-scoped features (e.g., .opencode/ directories, project config precedence).

Why this matters

  • Users often create a project to set up agents or project-specific config before committing.
  • Requiring an initial commit is unnecessary friction for configuration-only workflows.
  • opencode.json already signals intent to be a project; it should influence project detection.

Suggested implementation hints

  • In Project.fromDirectory, after the existing Git toplevel/common-dir checks, add a fallback:
    • If existsSync(path.join(directory, ".git")) OR Filesystem.findUp("opencode.json", directory) succeeds, generate a non-global project ID (e.g., hash of the absolute path) and set vcs: "git" if .git exists, else vcs: undefined.
  • Ensure migrateFromGlobal runs for these newly-recognized projects 4 .
  • Update tests to cover both cases.

Acceptance criteria

  • Directory with only .git (no commits) opens as a local project.
  • Directory with opencode.json (no Git) opens as a local project.
  • Existing behavior with committed Git repos is unchanged.
  • Sessions migrate from global to the new local project when applicable.

Notes

  • The UI determines “global” status by checking projectID === "global" or when metadata lacks an ID but has local overrides 5 .
  • Project configs already override global configs in the merge order, but they don’t affect the initial project ID assignment 6 .

Wiki pages you might want to explore:

Citations

File: packages/opencode/src/project/project.ts (L143-192)

        const top = await git(["rev-parse", "--show-toplevel"], {
          cwd: sandbox,
        })
          .then(async (result) => path.resolve(sandbox, (await result.text()).trim()))
          .catch(() => undefined)

        if (!top) {
          return {
            id,
            sandbox,
            worktree: sandbox,
            vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
          }
        }

        sandbox = top

        const worktree = await git(["rev-parse", "--git-common-dir"], {
          cwd: sandbox,
        })
          .then(async (result) => {
            const dirname = path.dirname((await result.text()).trim())
            if (dirname === ".") return sandbox
            return dirname
          })
          .catch(() => undefined)

        if (!worktree) {
          return {
            id,
            sandbox,
            worktree: sandbox,
            vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
          }
        }

        return {
          id,
          sandbox,
          worktree,
          vcs: "git",
        }
      }

      return {
        id: "global",
        worktree: "/",
        sandbox: "/",
        vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
      }

File: packages/opencode/src/project/project.ts (L294-314)

  async function migrateFromGlobal(id: string, worktree: string) {
    const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, "global")).get())
    if (!row) return

    const sessions = Database.use((db) =>
      db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(),
    )
    if (sessions.length === 0) return

    log.info("migrating sessions from global", { newProjectID: id, worktree, count: sessions.length })

    await work(10, sessions, async (row) => {
      // Skip sessions that belong to a different directory
      if (row.directory && row.directory !== worktree) return

      log.info("migrating session", { sessionID: row.id, from: "global", to: id })
      Database.use((db) => db.update(SessionTable).set({ project_id: id }).where(eq(SessionTable.id, row.id)).run())
    }).catch((error) => {
      log.error("failed to migrate sessions from global to project", { error, projectId: id })
    })
  }

File: packages/web/src/content/docs/config.mdx (L41-52)

### Precedence order

Config sources are loaded in this order (later sources override earlier ones):

1. **Remote config** (from `.well-known/opencode`) - organizational defaults
2. **Global config** (`~/.config/opencode/opencode.json`) - user preferences
3. **Custom config** (`OPENCODE_CONFIG` env var) - custom overrides
4. **Project config** (`opencode.json` in project) - project-specific settings
5. **`.opencode` directories** - agents, commands, plugins
6. **Inline config** (`OPENCODE_CONFIG_CONTENT` env var) - runtime overrides

This means project configs can override global defaults, and global configs can override remote organizational defaults.

File: packages/web/src/content/docs/config.mdx (L104-114)

### Per project

Add `opencode.json` in your project root. Project config has the highest precedence among standard config files - it overrides both global and remote configs.

:::tip
Place project specific config in the root of your project.
:::

When OpenCode starts up, it looks for a config file in the current directory or traverse up to the nearest Git directory.

This is also safe to be checked into Git and uses the same schema as the global one.

File: packages/app/src/context/layout.tsx (L343-356)

      const isGlobal = projectID === "global" || (metadata?.id === undefined && localOverride)
      if (!isGlobal) return base

      return {
        ...base,
        id: base.id ?? "global",
        name: local?.name,
        commands: local?.commands,
        icon: {
          url: base.icon?.url,
          override: local?.icon?.override,
          color: local?.icon?.color,
        },
      }

Activity

  1. added
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on Feb 26, 2026
  2. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Feb 26, 2026
  3. diegohb commented on Feb 26, 2026

    @diegohb
    Author

    Further context at this deepwiki chat and it also includes proposal to add cli command to convert a global project to local project on demand: https://deepwiki.com/search/how-do-i-change-a-project-of-g_3229a6c6-57fa-4bc0-9713-aa3818ca87e2?mode=fast

  4. changed the title [-][FEATURE]: Treat directories with .git or opencode.json as local projects (not global)[/-] [+][QOL]: Treat directories with .git or opencode.json as local projects (not global)[/+] on Feb 26, 2026
  5. assigned and unassigned on Apr 26, 2026
  6. removed
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  7. JtMotoX commented on May 5, 2026

    @JtMotoX

    Further context at this deepwiki chat and it also includes proposal to add cli command to convert a global project to local project on demand: https://deepwiki.com/search/how-do-i-change-a-project-of-g_3229a6c6-57fa-4bc0-9713-aa3818ca87e2?mode=fast

    @diegohb I really like that idea! That link seems to be dead. Any movement on that?

  8. JtMotoX commented on May 5, 2026

    @JtMotoX

    I found a workaround. If you create a .git/opencode file that contains a unique identifier, then restart opencode, it treats it as a local project.

    mkdir -p .git && openssl rand -hex 20 > .git/opencode && echo "Created .git/opencode with ID: $(cat .git/opencode)"
  9. epicwhale commented on Jun 18, 2026

    @epicwhale

    +1 to this pain point feels like a bug that a git init isn't enough for treating a directory as a project - and ive to actually wait till making the first commit?

  10. github-actions commented on Aug 18, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions