Skip to content

dev → main: hooks system maturity + goal/hook fixes + CI hardening#59

Merged
LeXwDeX merged 14 commits into
mainfrom
dev
Jul 3, 2026
Merged

dev → main: hooks system maturity + goal/hook fixes + CI hardening#59
LeXwDeX merged 14 commits into
mainfrom
dev

Conversation

@LeXwDeX

@LeXwDeX LeXwDeX commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Merges 14 commits from dev to main — the complete hooks system overhaul, goal-loop hardening, and CI improvements from Dev.1 through Dev.5.

PRs included

PR Title
#51 feat(hooks): move hooks config to dedicated hooks.json
#52 chore: cleanup openspec archived change artifacts
#53 feat(skill): add built-in configure-hooks skill
#54 fix(tool): resolve Goal.Service at execute time
#55 fix(prompt): remove stale always-on hooks system prompt
#56 fix(ci): designate a single bun cache saver per OS
#57 feat(hooks): dynamic Active Hooks + /create-hook + projector guard

Verification

Full changelog

v1.17.11-Dev.4...dev

Test and others added 14 commits July 3, 2026 15:45
…dependency

BREAKING: .claude/ directories and settings.json hooks field are no
longer read. Run /import-claude-hooks to migrate existing Claude
Code hook configurations.

Config location (6 layers → 2 dedicated files):
  ~/.config/opencode/hooks.json  (global, loaded once at startup)
  .opencode/hooks.json           (project, hot-reloaded via polling)
  <worktree>/.opencode/hooks.json (worktree, hot-reloaded)

Key changes:
- loadChain reads hooks.json from OpenCode-owned dirs only (.claude/
  and .local variants removed)
- Top-level event format (no wrapper); legacy {"hooks":{...}} tolerated
- readJSON filters via VALID_HOOK_EVENTS whitelist + Array.isArray
- Deprecation warning for hooks left in old settings.json
- Hot-reload: project-level only, interval polling (2s) instead of
  fs.watch (WSL2 DrvFs reliability); detects file deletion
- Global hooks.json is startup-only (no hot-reload), restart required
- Built-in /import-claude-hooks command for QA-guided migration
- AGENTS.md managed section (<!-- Hooks_START/END -->) for agent
  self-awareness of configured hooks

OpenSpec: hooks-config-independence (spec-driven, 43/43 tasks complete)
Tests: 21 hook tests (load-chain + settings-dedup + hot-reload)
feat(hooks): move hooks config to dedicated hooks.json, cut .claude/ dependency
chore: cleanup openspec archived change artifacts
Agents have no innate knowledge of opencode's hooks system — the format
lives only in source/docs, so without a prompt nudge an agent either
guesses wrong or never discovers hooks are possible. Registers a
customize-opencode-style built-in skill: description alone tells the
agent hooks exist and when to reach for them, full event/format/handler
reference loads lazily only when the skill is actually invoked.
feat(skill): add built-in configure-hooks skill
hooks.txt taught the retired settings.json 6-layer protocol (hooks now
live in dedicated hooks.json chains; .claude/ is never read) and stale
event counts (22 vs actual 27). Discovery is preserved via the built-in
configure-hooks skill injected through sys.skills(agent). Also fix
README hooks section: 27 events and dead hooks-reference.md link now
points at the skill doc.
The goal tool probed Effect.serviceOption(Goal.Service) in Tool.define's
build-phase init gen. ToolRegistry builds in an AppLayer mergeAll group
that cannot see sibling-group outputs, so the probe was always None and
the closure permanently no-op'd the tool ('goal service unavailable';
complete never ran markDone, the loop could not terminate via tool).

Move the probe into execute (request phase, full AppLayer context).
serviceOption keeps R = never, so no signature change; headless
runtimes still degrade gracefully. Document why task.ts's build-phase
probe is safe (SettingsHook arrives via provideMerge). Add an
integration test that mirrors the production layer topology (Goal
absent at build, provided at execute).
fix(tool): resolve Goal.Service at execute time, not ToolRegistry build
fix(prompt): remove stale always-on hooks system prompt
Concurrent Linux/Windows jobs across ci-test.yml, ci-typecheck.yml, and
release-fork.yml all computed the same {OS}-bun-{hash} cache key and raced
to save it, causing "Unable to reserve cache... another job may be
creating this cache" warnings and the cache never actually updating.
Now only one job per OS saves (ci-typecheck linux, ci-test e2e windows,
release-fork macos); everything else only restores.
fix(ci): designate a single bun cache saver per OS to stop save races
…jector guard

* feat(hooks): dynamic Active Hooks block + /create-hook command

- SettingsHook.list(): read-only scope-tagged summaries over hot-reloaded state
- SystemPrompt.hooks(): per-turn rendered block, zero tokens when no hooks
- /create-hook command: interactive hook authoring into hooks.json
- Remove static AGENTS.md Hooks_START snapshot (replaced by dynamic block)
- Fix import-claude-hooks.txt JSON format examples (missing hooks wrapper)
- Complete event list (27) and type list (5 incl mcp) in format reference

* fix(hooks): dedup skill descriptions + hot-reload mtime detection

- Export CustomizeOpencodeDescription / ConfigureHooksDescription from core,
  import in opencode instead of hardcoding (descriptions had already drifted)
- hot-reload: m > prev → m !== prev to cover cp -p / touch -t restoring
  older mtimes; reload is idempotent so the broader trigger is safe
- Add mtime-decrease regression test
- Include hooks-leftover-issues.md analysis document

* fix(core): guard projector against orphaned part updates after cleanup

PartUpdated events from interrupted streams can arrive after revert cleanup
has deleted the parent message. The projector now silently skips parts whose
parent message no longer exists instead of crashing with FOREIGN KEY
constraint failure. Includes regression test.

* chore: remove working notes file (hooks-leftover-issues.md)

Temporary analysis document from review process; content is now stale
(issues ①② fixed in this PR) or tracked separately (issue ③).

* refactor(hooks): address FABLE5 PR #57 review feedback

- Extract chainCandidates() shared by loadChain + summarizeChain (eliminate
  duplicated path logic and Global.Path.config fallback)
- Projector: add Effect.logWarning when skipping orphaned PartUpdated (was
  silent — genuine ordering bugs now observable in logs)
- descriptorFor: unify .slice(0,60) across all types (http/prompt/agent/mcp
  were not truncated)
- Integration test: real SettingsHook layer reaches sys.hooks() via
  serviceOption (not Layer.mock — proves the full chain works end-to-end)

---------

Co-authored-by: Test <test@opencode.test>
@LeXwDeX LeXwDeX enabled auto-merge (squash) July 3, 2026 15:48
@LeXwDeX LeXwDeX merged commit 041c129 into main Jul 3, 2026
20 of 23 checks passed
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.

1 participant