Skip to content

Feat : Add Extensible Hook Capability to OpenSpec Archive Operation #682

Open

Description

@dragonTalon

🚀 Feature Request Description

I propose adding an extensible Hook​ capability to OpenSpec's /opsx:archive supportcommand. This would allow us to inject custom consolidation logic when a specification (spec) is archived, meeting project-specific post-processing requirements.

📜 Context & Motivation

Currently, OpenSpec's archiving operation might be a relatively closed process. However, in practical use, we often need to perform deeper consolidation work based on archived changes.

A typical scenario is Error Log Management:

OpenSpec may generate an "error log" recording issues during Schema definition and execution. When certain problems are fixed by modifying the Schema, we hope that during the archiving of these changes, we can:
Automatically Identify: Associate the "errors" fixed by this archival.
Update Status: Move these "errors" from the "to-be-fixed" list to the "fixed" historical archive.
Organize Metadata: Perhaps also tag the fixed errors with version, archiving time, etc.
Without a Hook mechanism, this consolidation work requires manual effort or relies on external scripts that are difficult to synchronize atomically with the archiving process, making errors or omissions likely.

💡 Proposed Solution

I suggest referencing common CI/CD or Git Hook designs, reserving the ability to execute user-defined scripts at key nodes in the archiving workflow.
Hook Types:
pre-archiveHook: Triggered beforethe archive operation executes. Can be used for validation, data preparation, or preliminary consolidation.
post-archiveHook: Triggered afterthe archive operation completes. Ideal for follow-up actions like updating the error log, generating reports, sending notifications, etc.
Configuration Method:
The path to the Hook scripts could be specified in OpenSpec's configuration file (e.g., config.yaml).

Activity

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

    @21st-connector
    Contributor

    Task completed.

    The enrichment comment has been posted on issue #682. Here's a summary of what was included:

    Existing infrastructure: OpenSpec uses Commander.js preAction/postAction hooks internally for telemetry and validation, but has no user-extensible hook/plugin system.

    Archive code analysis: Identified the exact integration points in src/core/archive.ts where pre-archiveHook and post-archiveHook would fit within the 4-step archive workflow (validation → task check → spec updates → directory move).

    Configuration path: The ProjectConfigSchema in src/core/project-config.ts would need to be extended with hook fields, with Zod validation.

    Related issues: Linked 6 related issues — #328 (Claude Hooks), #331 (auto-update project.md on archive), #369 (post-archive actions), #641 (error correction log), plus archive bugs #412 and #498.

    Implementation considerations: Highlighted 4 key challenges — no existing subprocess spawning infrastructure, lack of atomicity/rollback in the current archive flow, error propagation contract design, and cross-platform script execution needs.


    View full conversation

    Powered by 1Code

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

    @21st-connector
    Contributor

    Issue Analysis

    Thanks for the detailed feature request. Here's an analysis based on the current codebase to help developers get started.

    Existing Hook Infrastructure

    OpenSpec already uses Commander.js .hook() for CLI lifecycle management, but these are internal framework hooks — not user-extensible:

    • Global preAction/postAction hooks in src/cli/index.ts:71-88 — used for telemetry and NO_COLOR setup
    • Command-specific preAction hooks — used for validation (src/commands/config.ts:31-37), deprecation warnings (src/cli/index.ts:210-212, src/commands/spec.ts:117-119), and experimental notices (src/commands/schema.ts:296-298)

    There is no existing user-extensible hook/plugin system, no EventEmitter pattern, and no middleware chain. Extensibility today is configuration-driven (openspec/config.yaml) rather than code-driven.

    Archive Command — Key Integration Points

    The archive implementation lives in src/core/archive.ts (340 lines), specifically the ArchiveCommand.execute() method at line 51. The current workflow is:

    1. Validation (lines 92-151) — validates proposal and delta specs
    2. Task progress check (lines 174-194) — warns about incomplete tasks
    3. Spec updates (lines 196-265) — finds and applies delta specs to main specs
    4. Directory move (lines 267-287) — moves change to openspec/changes/archive/<date>-<name>/

    A pre-archiveHook would logically fit between validation (step 1) and the spec update/move operations. A post-archiveHook would fit after step 4 (line 287), once the directory has been moved.

    Configuration Approach

    The project config is loaded in src/core/project-config.ts:66-161 from openspec/config.yaml (or .yml), validated via Zod (ProjectConfigSchema at line 19). The current schema supports:

    schema: string        # required
    context: string       # optional
    rules: Record<id, string[]>  # optional

    Adding hook configuration would require extending ProjectConfigSchema with new fields (e.g., hooks.pre-archive, hooks.post-archive pointing to script paths) and adding validation logic.

    Related Issues

    Several existing issues describe use cases that a hook system would address:

    Archive-related bugs that hook authors would need to be aware of:

    Implementation Considerations

    1. Script execution model: The codebase is pure TypeScript/ESM with no existing subprocess spawning for user scripts. A hook runner would need to be built from scratch — likely using Node.js child_process.execFile or spawn to run user-defined scripts.

    2. Atomicity concern (raised in the issue): The current archive operation is not transactional — spec updates (step 3) and the directory move (step 4) happen sequentially. If a post-archiveHook fails, rollback logic would need to be added, which doesn't exist today.

    3. Error propagation: The execute() method currently returns void and communicates via console.log / thrown errors. Hooks would need a defined contract for success/failure signaling (exit codes, stdout parsing, etc.).

    4. Cross-platform: The archive already handles Windows-specific issues (EPERM/EXDEV fallbacks at lines 36-48). Any hook execution path would also need Windows compatibility (e.g., .cmd/.ps1 scripts alongside shell scripts).

    🤖 Generated with Claude Code

  3. lsmonki commented on Feb 9, 2026

    @lsmonki

    This is closely related to the discussion in #557 — specifically this comment where I proposed lifecycle hooks as LLM instructions defined in schema.yaml, rather than shell scripts.

    The core idea: instead of spawning subprocesses, hooks are prompts that the LLM follows at lifecycle points (post-archive, post-sync, etc.):

    hooks:
      post-archive:
        instruction: |
          Review the archived change and cross-reference with docs/errors/pending.md.
          For each error that was fixed by this change:
          1. Move the entry from docs/errors/pending.md to docs/errors/resolved.md
          2. Tag it with the change name, resolution date, and affected specs
          3. If the fix involved a schema modification, note which fields changed
          Summarize what was consolidated at the end.

    This covers the error log consolidation scenario described here — without needing subprocess infrastructure, rollback mechanics, or cross-platform script handling.

    For cases where deterministic execution is needed (notifications, CI triggers, external API calls), hooks could also support a run field:

    hooks:
      post-archive:
        instruction: |
          Review the archived change and consolidate the error log...
        run: ./scripts/notify-slack.sh

    This way instruction handles the LLM-driven work (reasoning over artifacts, updating docs) and run handles the programmatic side (sending a webhook, updating a database). Both are optional — use one, the other, or both.

    Hooks at the schema level make sense for behavior that's inherent to the workflow — e.g., an ADR schema that always generates decision records on archive. But project-specific hooks (notify a particular Slack channel, update a project-specific error log, run a script that only exists in your repo) belong in the project config (openspec/config.yaml). The same mechanism could apply at both levels — schema defines default hooks for the workflow, config adds or overrides with project-specific ones.

    The only core change required is reading hooks from both schema.yaml and config.yaml, and surfacing them at the right moment. Everything else is defined by each schema or project, keeping the core minimal.

  4. dragonTalon commented on Feb 9, 2026

    @dragonTalon
    Author

    This is closely related to the discussion in #557 — specifically this comment where I proposed lifecycle hooks as LLM instructions defined in schema.yaml, rather than shell scripts.这与 #557 中的讨论密切相关——特别是在这条评论中,我建议将生命周期挂钩作为schema.yaml中定义的 LLM 指令,而不是 shell 脚本。

    The core idea: instead of spawning subprocesses, hooks are prompts that the LLM follows at lifecycle points (post-archive, post-sync, etc.):核心思想:挂钩不是生成子流程,而是 LLM 在生命周期点(归档后、同步后等)遵循的提示:

    hooks:
    post-archive:
    instruction: |
    Review the archived change and cross-reference with docs/errors/pending.md.
    For each error that was fixed by this change:
    1. Move the entry from docs/errors/pending.md to docs/errors/resolved.md
    2. Tag it with the change name, resolution date, and affected specs
    3. If the fix involved a schema modification, note which fields changed
    Summarize what was consolidated at the end.
    This covers the error log consolidation scenario described here — without needing subprocess infrastructure, rollback mechanics, or cross-platform script handling.这涵盖了此处描述的错误日志整合场景 - 无需子流程基础设施、回滚机制或跨平台脚本处理。

    For cases where deterministic execution is needed (notifications, CI triggers, external API calls), hooks could also support a run field:对于需要确定性执行的情况(通知、CI 触发器、外部 API 调用),挂钩还可以支持run字段:

    hooks:
    post-archive:
    instruction: |
    Review the archived change and consolidate the error log...
    run: ./scripts/notify-slack.sh
    This way instruction handles the LLM-driven work (reasoning over artifacts, updating docs) and run handles the programmatic side (sending a webhook, updating a database). Both are optional — use one, the other, or both.通过这种方式, instruction处理 LLM 驱动的工作(对工件进行推理、更新文档),而run处理编程方面(发送 Webhook、更新数据库)。两者都是可选的——使用一个、另一个或两者都使用。

    Hooks at the schema level make sense for behavior that's inherent to the workflow — e.g., an ADR schema that always generates decision records on archive. But project-specific hooks (notify a particular Slack channel, update a project-specific error log, run a script that only exists in your repo) belong in the project config (openspec/config.yaml). The same mechanism could apply at both levels — schema defines default hooks for the workflow, config adds or overrides with project-specific ones.模式级别的挂钩对于工作流固有的行为有意义 - 例如,始终在存档上生成决策记录的 ADR 模式。但是特定于项目的挂钩(通知特定的 Slack 通道、更新特定于项目的错误日志、运行仅存在于您的存储库中的脚本)属于项目配置 ( openspec/config.yaml )。相同的机制可以应用于两个级别 - 模式定义工作流程的默认挂钩,配置添加或覆盖特定于项目的挂钩。

    The only core change required is reading hooks from both schema.yaml and config.yaml, and surfacing them at the right moment. Everything else is defined by each schema or project, keeping the core minimal.唯一需要的核心更改是从schema.yaml和config.yaml读取hooks ,并在正确的时刻显示它们。其他一切都由每个模式或项目定义,保持核心最小化。

    I fully agree with your point, and I believe we should provide such capabilities for all commands. I think the artifacts defined in our schema.yaml should support hooks.

  5. mathieufortin01 commented on Jun 8, 2026

    @mathieufortin01

    Will use this. While syncing the canonical spec.md we want to populate a Change History table. Only way currently is to add directives in a CLAUDE.md at openspec root. Havent found other ways that worked correctly, but this pre/post hooks would allow such things.

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

    design-reviewNeeds product/design decision

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions