Skip to content

openspec init --tools none does not create openspec/config.yaml #712

Closed

Description

@akoi

Description

When running openspec init --tools none (e.g. when managing skills/commands externally via a global agent config), the command creates the openspec/ directory structure (changes/, specs/) but does not create an openspec/config.yaml file.

Steps to Reproduce

  1. Run openspec init --tools none in a project
  2. Check the openspec/ directory

Actual Behavior

openspec/
├── changes/
└── specs/

No config.yaml is created.

Expected Behavior

openspec/
├── changes/
├── specs/
└── config.yaml

config.yaml should always be created during init, regardless of the --tools setting. This file is where users define project-level configuration such as:

  • Default schema (schema: spec-driven)
  • Project context (tech stack, conventions, domain knowledge)
  • Per-artifact rules

The --tools flag controls which AI tool integrations get set up (skills, slash commands, etc.), but config.yaml is an OpenSpec-level project config file, not a tool integration file. Its creation should not be gated by the tools selection.

Use Case

This is particularly relevant for users who manage their AI tool configuration externally (e.g., a shared/global config repo for OpenCode skills) and use --tools none because they don't need per-project tool integration files. They still need config.yaml to define their project context and schema.

Environment

  • OpenSpec version: 1.1.1
  • OS: macOS (darwin)

Activity

  1. 21st-connector commented on Feb 14, 2026

    @21st-connector
    Contributor

    Task completed.

    The enrichment comment has been posted to issue #712. Here's a summary of the findings:

    Root Cause: The canPromptInteractively() method in src/core/init.ts:141 returns false whenever --tools is provided (any value). The createConfig() method at line 496 then skips config creation when not interactive and --force isn't set. So openspec init --tools none (without --force) silently skips config.yaml.

    The fix is straightforward — decouple config file creation from the interactivity check, since config.yaml is project-level configuration, not an interactive prompt artifact.

    Test gap: The existing test at test/core/init.test.ts:144 masks this bug by always passing force: true alongside tools: 'none'.

    Related issues: #385, #653, and #619 provide additional context but this is not a duplicate.


    View full conversation

    Powered by 1Code

  2. 21st-connector commented on Feb 14, 2026

    @21st-connector
    Contributor

    Issue Analysis

    Root Cause

    The bug is in src/core/init.ts — specifically in the interaction between canPromptInteractively() (line 141) and createConfig() (line 485).

    When --tools is passed (any value, including none), canPromptInteractively() returns false:

    // src/core/init.ts:141-145
    private canPromptInteractively(): boolean {
      if (this.interactiveOption === false) return false;
      if (this.toolsArg !== undefined) return false;  // <-- triggers when --tools is passed
      return isInteractive({ interactive: this.interactiveOption });
    }

    Then in createConfig() (line 496), config creation is skipped when not interactive and --force is not set:

    // src/core/init.ts:496-498
    if (!this.canPromptInteractively() && !this.force) {
      return 'skipped';
    }

    So when a user runs openspec init --tools none (without --force), the config file is silently skipped. The guard at line 496 was intended to prevent config creation in CI/non-interactive pipelines, but it unintentionally gates config creation on the --tools flag as well.

    Relevant Files

    File Lines Purpose
    src/core/init.ts 141-145 canPromptInteractively() — returns false when --tools is provided
    src/core/init.ts 485-507 createConfig() — skips creation when non-interactive and no --force
    src/core/init.ts 84-122 execute() — main init flow, calls createConfig at line 118
    src/cli/index.ts 93-130 CLI command definition for init
    test/core/init.test.ts 144-156 Existing test for --tools none (uses force: true, masking this bug)

    Suggested Fix

    The createConfig() method should not be gated by canPromptInteractively(). The config file is project-level configuration, not an interactive prompt. A minimal fix:

    // src/core/init.ts:485-507
    private async createConfig(openspecPath: string, extendMode: boolean): Promise<'created' | 'exists' | 'skipped'> {
      const configPath = path.join(openspecPath, 'config.yaml');
      const configYmlPath = path.join(openspecPath, 'config.yml');
    
      if (fs.existsSync(configPath) || fs.existsSync(configYmlPath)) {
        return 'exists';
      }
    
      try {
        const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
        await FileSystemUtils.writeFile(configPath, yamlContent);
        return 'created';
      } catch {
        return 'skipped';
      }
    }

    This removes the non-interactive guard entirely — config.yaml should always be created if it doesn't already exist, regardless of interactivity or --tools selection.

    If there's a reason to preserve the non-interactive guard for true CI environments (no --tools flag, piped stdin), a more targeted fix would be to decouple canPromptInteractively from the --tools flag for config purposes:

    if (!this.force && !this.toolsArg && !isInteractive({ interactive: this.interactiveOption })) {
      return 'skipped';
    }

    Test Gap

    The existing test at test/core/init.test.ts:144-156 passes force: true alongside tools: 'none', which bypasses the guard. A new test case should verify config creation without --force:

    it('should create config.yaml with --tools none even without --force', async () => {
      const initCommand = new InitCommand({ tools: 'none' });
      await initCommand.execute(testDir);
    
      const configPath = path.join(testDir, 'openspec', 'config.yaml');
      expect(await fileExists(configPath)).toBe(true);
    });

    Related Issues

    This is not a duplicate of any existing issue.

  3. akoi commented on Feb 14, 2026

    @akoi
    Author

    Quick fix is to use the --force flag when initialising with --tools none:
    openspec init --tools none --force

  4. clay-good commented on Jul 22, 2026

    @clay-good
    Collaborator

    Closing as fixed. The guard that skipped config creation for non-interactive setups was removed in the stores rework (#1190, commit a0decbe), released in v1.5.0.

    Verified on current main: openspec init --tools none now produces changes/, specs/, and config.yaml. The config file no longer requires a tool entourage to show up.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions