Skip to content

Conversation

@IgorWarzocha
Copy link
Owner

Summary

  • Implements hot-reload functionality for agents, skills, and configuration files
  • Automatically reloads resources when files change without requiring restart
  • Added comprehensive test coverage for hot-reload scenarios

Changes

  • Agent hot-reload: Agents automatically reload when their definition changes
  • Skill hot-reload: Skills dynamically reload on file modifications
  • Config hot-reload: Configuration changes are applied in real-time
  • File watcher improvements: Enhanced file watching with debouncing and error handling
  • Global sync context: React context for coordinating hot-reload across components

Tests

  • Added config-delete-hot-reload.test.ts - tests config deletion and reload behavior
  • Added skill-hot-reload.test.ts - comprehensive skill hot-reload scenarios

Files changed

  • 24 files modified
  • Key packages: packages/opencode, packages/app
  • New test fixtures and planning docs

OpenCode Agent added 3 commits January 15, 2026 17:31
- Added OPENCODE_EXPERIMENTAL_HOT_RELOAD_AGENTIC and OPENCODE_EXPERIMENTAL_HOT_RELOAD_CONFIG flags
- Extended FileWatcher to support watching config directories with context preservation
- Implemented initWatcher logic for Config, Skill, Agent, Command, and ToolRegistry
- Added TUI and App event handlers for live updates
- Enhanced instance context management for async watcher callbacks
- Added tests for config and skill hot reloading
@gemini-code-assist
Copy link

Summary of Changes

Hello @IgorWarzocha, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a significant enhancement by integrating hot-reload capabilities across key components of the application, including agents, skills, and configuration. The primary goal is to streamline development and improve operational efficiency by enabling real-time application of changes without manual restarts. This involves foundational changes to the file watching mechanism, event bus, and component initialization, ensuring a responsive and dynamic user experience.

Highlights

  • Hot-Reload Implementation: Introduced hot-reload functionality for agents, skills, and configuration files, allowing automatic updates without requiring a full application restart.
  • Enhanced File Watching: Improved the file watcher with static require() bindings for better bundler compatibility, context re-entry for event publishing, and the ability to watch configuration directories.
  • Dynamic Configuration Updates: Agents, commands, and tools now dynamically reload their configurations and definitions in response to changes in opencode.json, opencode.jsonc, and SKILL.md files.
  • UI Integration: Integrated hot-reload events into both the TUI and web application contexts, ensuring that UI components reflect the latest agent, command, and skill definitions.
  • Robustness and Testing: Added file existence checks during markdown parsing to handle deleted files gracefully and included comprehensive new test suites for config and skill hot-reload scenarios.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@IgorWarzocha
Copy link
Owner Author

@codex review this PR.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-planned hot-reload feature for agents, skills, and configurations. The implementation is comprehensive, with changes spanning both backend and frontend components, and the use of experimental flags provides a good safety net. The refactoring of the file watcher and instance context management is a solid improvement. However, I've identified a few high-severity issues related to incorrect state invalidation in the hot-reload logic and a potential runtime error in the TUI. There are also some minor type safety concerns that should be addressed. Overall, this is a valuable addition that will greatly improve the development workflow.

setAgentStore("current", agents()[0].name)
return agents()[0]
}
return agents()[0]!

Choose a reason for hiding this comment

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

high

There's a potential runtime error here. If agents() returns an empty array, the if (agents().length > 0) block is skipped, and this line will be executed. At this point, agents()[0] is undefined, and using the non-null assertion ! will cause the application to crash. You should handle the case where no agents are available more gracefully, for example by returning undefined and letting the consumer of this function handle it.

          return undefined

Comment on lines 1248 to 1255
try {
// Invalidate and reload state
const result = await state()
Bus.publish(Events.Updated, result.config)
log.info("config reloaded successfully")
} catch (err) {
log.error("failed to reload config", { error: err })
}

Choose a reason for hiding this comment

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

high

The hot-reload logic for config appears to be incorrect. Simply calling await state() will return the cached configuration because the state is not being invalidated. This will prevent the hot-reload from actually taking effect.

You should refactor this to use Instance.invalidate before reloading the state, following the pattern used in packages/opencode/src/command/index.ts.

  1. Extract the async initializer function from Instance.state into a named function (e.g., initState).
  2. Change the state definition to export const state = Instance.state(initState);.
  3. In initWatcher, call await Instance.invalidate(initState) before fetching the new state.

Comment on lines 150 to 157
try {
// Reload skills and emit update event
await state()
Bus.publish(Events.Updated, {})
log.info("skills reloaded successfully")
} catch (err) {
log.error("failed to reload skills", { error: err })
}

Choose a reason for hiding this comment

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

high

This hot-reload logic has the same issue as the one in config.ts. Calling await state() will return the cached list of skills without reloading them from the filesystem because the state cache is not being invalidated.

To fix this, please apply the same initState and Instance.invalidate pattern used in other parts of the codebase (like command/index.ts):

  1. Extract the async initializer from Instance.state into a named initState function.
  2. Define export const state = Instance.state(initState);.
  3. In initWatcher, call await Instance.invalidate(initState) before await state() to ensure the skills are reloaded from scratch.

const unsub = globalSDK.event.listen((e) => {
const directory = e.name
const event = e.details
const event = e.details as any

Choose a reason for hiding this comment

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

medium

Using as any for e.details bypasses TypeScript's type safety and can lead to runtime errors if the event structure changes. It's better to cast to unknown and use type guards, or at least provide a more specific partial type to ensure some level of safety.

    const event = e.details as { type: string; [key: string]: unknown };


sdk.event.listen((e) => {
const event = e.details
const event = e.details as any

Choose a reason for hiding this comment

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

medium

The use of as any for e.details compromises type safety. This can hide bugs and make the code harder to maintain. Please consider using a more specific type, or casting to unknown and using type guards to safely access event properties.

      const event = e.details as { type: string; properties: any };

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6de5f81508

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 1248 to 1251
try {
// Invalidate and reload state
const result = await state()
Bus.publish(Events.Updated, result.config)

Choose a reason for hiding this comment

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

P2 Badge Invalidate config state on hot-reload

The hot-reload handler calls state() directly, but Config.state is memoized via Instance.state, so this just returns the cached config instead of re-reading from disk; as a result, edits to opencode.json (and any dependent changes like new agents/commands or tool dirs) won’t actually be reflected even though config.updated is emitted. You likely need to invalidate the cached state (e.g., via Instance.invalidate(...)) before calling state() here.

Useful? React with 👍 / 👎.

Comment on lines 150 to 153
try {
// Reload skills and emit update event
await state()
Bus.publish(Events.Updated, {})

Choose a reason for hiding this comment

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

P2 Badge Skill hot-reload never refreshes cached skills

The skill watcher also calls state() without invalidating the Instance.state cache, so skill additions/edits/deletions keep returning the old skill list and the “skill.updated” event fires with stale data. Any change to SKILL.md will therefore not show up in Skill.all() unless the instance is restarted.

Useful? React with 👍 / 👎.

Comment on lines 59 to 62
await Promise.all(pending)
GlobalBus.emit("event", {
directory: Instance.directory,
payload,

Choose a reason for hiding this comment

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

P2 Badge GlobalBus events are dropped if any subscriber fails

Because Bus.publish now awaits all subscriber promises before emitting to GlobalBus, any subscriber rejection (e.g., ShareNext sync failing on a network error) will prevent the global event from ever being emitted; this means the TUI/server event streams can silently stop updating when a single subscriber throws. Consider isolating subscriber failures or emitting to GlobalBus regardless of subscriber success.

Useful? React with 👍 / 👎.

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.

2 participants