Skip to content

[codex] Fix workspace.yaml collision detection#1165

Merged
TabishB merged 8 commits into
mainfrom
codex/fix-workspace-yaml-collision
Jun 3, 2026
Merged

[codex] Fix workspace.yaml collision detection#1165
TabishB merged 8 commits into
mainfrom
codex/fix-workspace-yaml-collision

Conversation

@TabishB

@TabishB TabishB commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1164.

This moves beta workspace view state out of the repository root and into OpenSpec-owned metadata. A project-level workspace.yaml is now always ignored by workspace discovery, so Dagster's required workspace.yaml no longer redirects openspec update into the beta workspace update path.

It also removes the top-level openspec update auto-route into workspace update. openspec update may detect a real OpenSpec workspace and print guidance, but it no longer mutates workspace-local guidance, skills, view state, links, registry state, or linked repos. Workspace-local refreshes stay behind the explicit openspec workspace update command.

Root Cause

The stable openspec update path checked findWorkspaceRoot() before running the normal repo-local update. The beta workspace detector treated any ancestor workspace.yaml as OpenSpec-owned, so Dagster projects with their own required workspace.yaml were redirected into workspace update and failed strict OpenSpec workspace-state parsing.

What Changed

  • Current OpenSpec workspace view state now lives at .openspec-workspace/view.yaml.
  • Root workspace.yaml is no longer treated as OpenSpec current state or legacy state.
  • Legacy workspace split-state remains explicitly namespaced as .openspec-workspace/workspace.yaml plus .openspec-workspace/local.yaml.
  • Removed allowUnmarkedViewState; workspace-root detection is uniformly strict for current and already-known paths.
  • Stopped top-level openspec update from invoking workspace update; actual workspace roots now fail with openspec workspace update guidance and no workspace mutation.
  • Updated docs, workspace-foundation and CLI-update spec text, repair guidance, and the patch changeset.
  • Kept regressions for Dagster-style workspace.yaml files in workspace detection, planning-home resolution, and openspec update.

Validation

  • pnpm run build
  • pnpm run lint
  • pnpm exec vitest run test/core/workspace/foundation.test.ts test/core/workspace/legacy-state.test.ts test/core/planning-home.test.ts test/commands/workspace.test.ts test/commands/workspace-initiative-open.test.ts test/commands/workspace.interactive.test.ts test/commands/config-profile.test.ts
  • pnpm exec vitest run test/commands/workspace.test.ts test/core/workspace/foundation.test.ts test/core/planning-home.test.ts test/core/update.test.ts test/cli-e2e/basic.test.ts test/commands/config-profile.test.ts
  • pnpm test (rerun after settled build: 89 files, 1660 tests)
  • node dist/cli/index.js validate cli-update --specs
  • Manual built-CLI repro: Dagster-shaped root workspace.yaml exits 0 through normal update, remains unchanged, and no .openspec-workspace, changes, or .code-workspace artifacts are created.
  • Manual built-CLI repro: actual .openspec-workspace/view.yaml workspace root exits 1 with openspec workspace update guidance and leaves view state unchanged.

Summary by CodeRabbit

  • New Features

    • Workspace discovery now ignores non-OpenSpec workspace manifests for better compatibility with Dagster repos.
  • Bug Fixes

    • Workspace view state is stored in a private metadata folder (created automatically) to avoid root-level conflicts.
  • Behavior Changes

    • Running repo-local update inside a workspace will no longer perform workspace-local updates; users are guided to run the workspace update command.
  • Documentation

    • Docs and CLI hints updated to reference the new private view-state location.
  • Tests

    • Test suite updated to reflect the new metadata location and behaviors.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR relocates OpenSpec's workspace view state from workspace.yaml at workspace roots into .openspec-workspace/view.yaml, updates the filename constant and path resolution, ensures the metadata directory is created before writes, changes openspec update routing at workspace roots, and updates tests, docs, and repair hints to match; foreign root workspace.yaml files are ignored.

Changes

Workspace View State Consolidation

Layer / File(s) Summary
View state filename and path resolution
src/core/workspace/foundation.ts, src/core/workspace/legacy-state.ts
WORKSPACE_VIEW_STATE_FILE_NAME changed to view.yaml; getWorkspaceViewStatePath() now resolves under .openspec-workspace/; legacy shared-state remains workspace.yaml.
State I/O and directory creation
src/core/workspace/state-io.ts, test/core/workspace/legacy-state.test.ts
writeWorkspaceViewState() serializes content, creates the metadata directory (getWorkspaceMetadataDir(...)) before writing, and tests ensure the parent directory exists for view state.
Local-project detection and update routing
src/cli/index.ts, test/commands/workspace.test.ts, test/core/planning-home.test.ts
openspec update now runs repo-local updates directly when an openspec directory exists under the target; when inside a workspace root, openspec update errors instructing openspec workspace update. Tests added/updated to ensure repo-local nested targets and foreign root workspace.yaml files are ignored for workspace routing.
Command surface and selection helpers
src/commands/workspace.ts, src/commands/workspace/selection.ts
Removed WorkspaceCommand.updateRoot(...), runWorkspaceUpdateForRoot(...), and selectWorkspaceRootForCommand(...), consolidating workspace selection flows.
Error messages and guidance text
src/commands/workspace/operations.ts, src/commands/workspace/context-status.ts, src/commands/workspace/open-view.ts, src/core/workspace/open-surface.ts
Updated repair hints and guidance to reference .openspec-workspace/view.yaml; open-view adds conditional advice for registry selectors.
Docs, specs, and release note
docs/concepts.md, docs/workspaces-beta/user-guide.md, openspec/specs/workspace-foundation/spec.md, .changeset/fuzzy-dagster-workspaces.md, docs/cli.md, openspec/specs/cli-update/spec.md
Documentation and spec text updated to record .openspec-workspace/view.yaml as the canonical private view record and to describe the updated discovery/update behavior; changeset added.
Planning-home and initialization tests
test/core/planning-home.test.ts, test/commands/workspace-initiative-open.test.ts, test/core/workspace/foundation.test.ts
Fixtures and tests updated to create/use .openspec-workspace/view.yaml, to expect the metadata directory to be created during workspace initialization, and to ensure unmarked root workspace.yaml files are ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • alfred-openspec

"🐰 I nudged the view file out of sight,
into .openspec-workspace where it sleeps at night.
No Dagster collision, no parsing fright,
updates run steady, and tests shine bright."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: fixing workspace.yaml collision detection by moving OpenSpec state to .openspec-workspace/view.yaml, preventing non-OpenSpec files from being misidentified.
Linked Issues check ✅ Passed The PR fully addresses issue #1164 by moving workspace view state to .openspec-workspace/view.yaml, stopping acceptance of bare top-level workspace.yaml as OpenSpec state, preserving legacy namespaced state, and preventing crashes on foreign workspace.yaml files.
Out of Scope Changes check ✅ Passed All changes are directly related to the workspace.yaml collision issue: relocating view state, updating workspace detection logic, removing root-only update paths, updating tests/docs, and clarifying openspec update vs workspace update behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-workspace-yaml-collision

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@TabishB TabishB marked this pull request as ready for review June 3, 2026 08:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/workspace/foundation.ts`:
- Around line 79-81: Tests and registry currently ignore legacy root-level
workspace.yaml, so implement an explicit migration that detects a root-level
"workspace.yaml" and backfills it into the managed metadata directory used by
getWorkspaceViewStatePath/getWorkspaceMetadataDir (or otherwise imports it into
the same shape read by isWorkspaceRoot and readWorkspaceViewState*), and ensure
registry enumeration in registry.ts picks up those migrated workspaces; add a
startup migration function (e.g., migrateLegacyRootWorkspaceYaml) that runs
before workspace enumeration and moves/parses root-level workspace.yaml into
.openspec-workspace/workspace.yaml (or writes the normalized view.yaml) for
known managed/registry workspaces, and update
isWorkspaceRoot/readWorkspaceViewState*/registry.ts to treat the migrated file
as authoritative.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 02808ee6-cd17-4340-9083-bb3b3cdf6d39

📥 Commits

Reviewing files that changed from the base of the PR and between c590929 and bc31327.

📒 Files selected for processing (14)
  • .changeset/fuzzy-dagster-workspaces.md
  • docs/concepts.md
  • docs/workspaces-beta/user-guide.md
  • openspec/specs/workspace-foundation/spec.md
  • src/commands/workspace/context-status.ts
  • src/commands/workspace/open-view.ts
  • src/commands/workspace/operations.ts
  • src/core/workspace/foundation.ts
  • src/core/workspace/legacy-state.ts
  • src/core/workspace/open-surface.ts
  • src/core/workspace/state-io.ts
  • test/core/planning-home.test.ts
  • test/core/workspace/foundation.test.ts
  • test/core/workspace/legacy-state.test.ts
✅ Files skipped from review due to trivial changes (4)
  • docs/workspaces-beta/user-guide.md
  • src/core/workspace/open-surface.ts
  • .changeset/fuzzy-dagster-workspaces.md
  • src/commands/workspace/operations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/core/planning-home.test.ts

Comment on lines 79 to 81
export function getWorkspaceViewStatePath(workspaceRoot: string): string {
return joinWorkspacePath(workspaceRoot, WORKSPACE_VIEW_STATE_FILE_NAME);
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_VIEW_STATE_FILE_NAME);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Any code still reading a root-level workspace.yaml (outside .openspec-workspace/)?
rg -nP -C3 "workspace\.yaml" --type=ts -g '!**/*.test.ts'

# 2) Any migration/backfill for relocating legacy root state?
rg -nP -C4 '(migrat|backfill|legacyRoot|rootViewState|relocat)' --type=ts -g 'src/**'

# 3) How getWorkspaceViewStatePath / legacy shared path are consumed across src.
rg -nP -C3 '(getWorkspaceViewStatePath|getWorkspaceLegacySharedStatePath)' --type=ts -g 'src/**'

Repository: Fission-AI/OpenSpec

Length of output: 22057


🏁 Script executed:

#!/bin/bash
# Find any root-level filesystem probing for "workspace.yaml" (not under .openspec-workspace)
rg -nP --type=ts '(workspaceRoot\s*,\s*["'\'']workspace\.yaml["'\'']|joinWorkspacePath\([^)]*workspaceRoot[^)]*["'\'']workspace\.yaml["'\'']|joinPath\([^)]*workspaceRoot[^)]*["'\'']workspace\.yaml["'\'']|path.*workspace\.yaml)' src test

# Inspect workspace root detection/read path logic around view + legacy workspace.yaml
rg -nP --type=ts 'function readWorkspaceViewState|function isWorkspaceRoot|getWorkspaceLegacySharedStatePath|getWorkspaceViewStatePath' src/core/workspace/state-io.ts src/core/workspace/foundation.ts src/core/workspace/legacy-state.ts

# Locate the test mentioned in the original comment
rg -nP 'ignores unmarked root view state' test --type=ts
rg -nP 'unmarked root view state' test --type=ts
rg -nP 'root view state' test --type=ts

Repository: Fission-AI/OpenSpec

Length of output: 3112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search in src for any reads/writes/migrations involving root-level workspace.yaml and view.yaml
rg -nP --type=ts 'workspace\.yaml' src/core src/commands | head -n 200

rg -nP --type=ts 'view\.yaml|WORKSPACE_VIEW_STATE_FILE_NAME|WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME|getWorkspaceViewStatePath|getWorkspaceLegacySharedStatePath' src/core src/commands | head -n 200

# Look specifically for "migration" or "backfill" that mentions workspace state.
rg -nP --type=ts '(migrat|backfill|legacyRoot|root view state|relocat|orphan)' src/core/workspace src/core src/commands | head -n 200

# Inspect the relevant test file around the "ignores unmarked root view state" case.
sed -n '200,340p' test/core/workspace/foundation.test.ts

Repository: Fission-AI/OpenSpec

Length of output: 10675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant constants + functions for workspace metadata dir + view state path
sed -n '1,140p' src/core/workspace/foundation.ts
echo '---'
sed -n '1,120p' src/core/workspace/legacy-state.ts
echo '---'
sed -n '1,230p' src/core/workspace/state-io.ts

echo '---'
# Search for any code that might copy/migrate root-level workspace.yaml into .openspec-workspace
rg -nP --type=ts "workspaceRoot.*workspace\.yaml|path\.join\([^)]*workspaceRoot[^)]*workspace\.yaml|joinWorkspacePath\([^)]*workspaceRoot[^)]*workspace\.yaml|\.openspec-workspace'.workspace\.yaml|copyFile.*workspace\.yaml|rename.*workspace\.yaml|move.*workspace\.yaml|relocat|migrat" src/core src/commands
echo '---'
# Search for init/update referencing workspace state migration
rg -nP --type=ts "writeWorkspaceViewState|readWorkspaceViewState|readWorkspaceViewStateSync|workspace\.yaml|view\.yaml|relink|repair .*view\.yaml|orphan" src/core src/commands

Repository: Fission-AI/OpenSpec

Length of output: 20247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Any non-constant usage of root-level workspace.yaml inside src (not tests)
rg -nP --type=ts "workspace\.yaml" src/core/workspace src/commands/workspace src/core/planning-home.ts src/core/registry* 2>/dev/null || true

# 2) Any direct mentions of root-level state backfill/migration in workspace area
rg -nP --type=ts "(migrat|backfill|relocat|legacy).*(workspace|view|state)" src/core/workspace src/commands/workspace || true

# 3) Any code that reads/writes legacy shared state file or workspace view state beyond state-io
rg -nP --type=ts "(getWorkspaceLegacySharedStatePath|readWorkspaceViewState|writeWorkspaceViewState|workspaceStatePartsToViewState|WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME|getWorkspaceViewStatePath)" src/core/workspace src/commands/workspace || true

# 4) Inspect registry + managed workspace root detection
sed -n '1,260p' src/core/workspace/registry.ts || true
sed -n '260,520p' src/core/workspace/registry.ts || true

Repository: Fission-AI/OpenSpec

Length of output: 12045


Add/confirm migration/backfill for legacy root-level workspace.yaml so managed/registry workspaces aren’t orphaned

  • isWorkspaceRoot / readWorkspaceViewState* only probe .openspec-workspace/view.yaml and .openspec-workspace/workspace.yaml (not <root>/workspace.yaml), and managed workspace enumeration (registry.ts) relies on that same detection.
  • The test suite enforces the behavior that root-level workspace.yaml is ignored even when its contents look OpenSpec-shaped.
  • Please ensure there’s an explicit backfill/migration for known managed/registry workspaces (or document the behavior) so existing beta users don’t silently lose their workspace state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/workspace/foundation.ts` around lines 79 - 81, Tests and registry
currently ignore legacy root-level workspace.yaml, so implement an explicit
migration that detects a root-level "workspace.yaml" and backfills it into the
managed metadata directory used by
getWorkspaceViewStatePath/getWorkspaceMetadataDir (or otherwise imports it into
the same shape read by isWorkspaceRoot and readWorkspaceViewState*), and ensure
registry enumeration in registry.ts picks up those migrated workspaces; add a
startup migration function (e.g., migrateLegacyRootWorkspaceYaml) that runs
before workspace enumeration and moves/parses root-level workspace.yaml into
.openspec-workspace/workspace.yaml (or writes the normalized view.yaml) for
known managed/registry workspaces, and update
isWorkspaceRoot/readWorkspaceViewState*/registry.ts to treat the migrated file
as authoritative.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/index.ts`:
- Around line 169-175: The current guard uses findWorkspaceRoot(resolvedPath)
and always throws for any path under a workspace, which blocks repo-local
projects; instead, first detect whether the resolvedPath itself contains a
repo-local OpenSpec project (use the existing repo-local detection helper or the
same logic UpdateCommand.execute uses to identify repo-local projects for the
target path). Only if no repo-local project exists should you call
findWorkspaceRoot(resolvedPath) and then throw the workspace-specific error;
additionally ensure you only treat the workspace as blocking when the
resolvedPath is a planning-home path without its own repo-local project (i.e.,
defer the workspaceRoot check until after repo-local detection).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c63661e9-29b4-4c4e-975a-c4164a16bedd

📥 Commits

Reviewing files that changed from the base of the PR and between bc31327 and cb33487.

📒 Files selected for processing (6)
  • .changeset/fuzzy-dagster-workspaces.md
  • docs/cli.md
  • openspec/specs/cli-update/spec.md
  • src/cli/index.ts
  • src/commands/workspace.ts
  • test/commands/workspace.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fuzzy-dagster-workspaces.md

Comment thread src/cli/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/index.ts`:
- Around line 104-105: The current catch block that unconditionally returns
false should be narrowed to only swallow "not found" filesystem errors; change
the anonymous catch to catch (err) and if (err?.code === 'ENOENT' || err?.code
=== 'ENOTDIR') return false, otherwise rethrow the error so permission/IO
failures bubble up; update the catch around the filesystem check (the try/catch
that currently uses `} catch { return false;`) to implement this conditional
handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3fa31471-4146-4a6f-901f-dc9ce2b926ae

📥 Commits

Reviewing files that changed from the base of the PR and between bca019f and 6236a31.

📒 Files selected for processing (4)
  • docs/concepts.md
  • openspec/specs/cli-update/spec.md
  • src/cli/index.ts
  • test/commands/workspace.test.ts
✅ Files skipped from review due to trivial changes (2)
  • openspec/specs/cli-update/spec.md
  • docs/concepts.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/commands/workspace.test.ts

Comment thread src/cli/index.ts Outdated
alfred-openspec
alfred-openspec previously approved these changes Jun 3, 2026

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the workspace/update split and Dagster-style workspace.yaml regression coverage. The change keeps repo-local update behavior intact, moves workspace view state under .openspec-workspace/view.yaml, avoids mutating workspace state from top-level openspec update, and CI is green. Looks good to merge.

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the workspace/update split after the latest fixes. The Dagster-style workspace.yaml path now stays repo-local without side effects, actual workspace roots give explicit workspace-update guidance, CodeRabbit's catch-block issue is fixed, and CI plus local build/lint/targeted tests are green.

@TabishB TabishB added this pull request to the merge queue Jun 3, 2026
Merged via the queue into main with commit 0a01146 Jun 3, 2026
12 checks passed
@TabishB TabishB deleted the codex/fix-workspace-yaml-collision branch June 3, 2026 09:22
linjinze999 pushed a commit to linjinze999/OpenSpec that referenced this pull request Jun 4, 2026
* Fix workspace.yaml collision detection

* Store workspace view state under metadata

* Keep top-level update out of workspace updates

* Remove unused workspace root selector

* Allow repo updates below workspace roots

* Propagate repo state probe errors

* Generalize workspace yaml collision coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openspec update crashes when a non-OpenSpec workspace.yaml exists in the project root (filename collision with Dagster)

2 participants