Skip to content

fix(desktop): aivis タスク完了通知が二重に読み上げられる問題を修正#324

Merged
MocA-Love merged 3 commits intomainfrom
fix/aivis-duplicate-completion-notification
Apr 18, 2026
Merged

fix(desktop): aivis タスク完了通知が二重に読み上げられる問題を修正#324
MocA-Love merged 3 commits intomainfrom
fix/aivis-duplicate-completion-notification

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 18, 2026

概要

Aivis によるタスク完了の音声読み上げが、1回の完了に対してほぼ同時に二重で再生されてしまう不具合を修正します。通知音(リングトーン)は短い効果音のため重複が聞き取りづらかったものの、実際には同様に二重発火していました。

原因

Codex の完了通知経路が2本存在し、同じ完了イベントが notify.sh/hook/complete に2回到達していたのが主因です。

  1. `~/.codex/hooks.json` の Stop hook
  2. Codex wrapper の `--enable codex_hooks -c 'notify=["bash","…notify.sh"]'` オプション

両経路がそれぞれ `/hook/complete` を叩くため、`notificationsEmitter` から `AGENT_LIFECYCLE` が2回発火し、`NotificationManager.handleAgentLifecycle` → `playAivisNotification` が2回呼ばれていました。

加えて、`MainWindow()` 内で `notificationsApp.listen` / `new NotificationManager` / `notificationsEmitter.on(AGENT_LIFECYCLE, ...)` を初期化していたため、macOS で全ウィンドウを閉じてから Dock 再アクティベートする経路 (`app.on("activate")` → `MainWindow()` 再実行) でリスナーが多重登録される構造的リスクも残っていました。

Claude Code / OpenCode / Cursor / Copilot / Gemini は通知経路が1本のため影響なく、本件は Codex セッション終了時に発生していました。

修正内容

1. Codex wrapper の `notify=` 注入を除去

`apps/desktop/src/main/lib/agent-setup/templates/codex-wrapper-exec.template.sh`

```diff
-"$REAL_BIN" --enable codex_hooks -c 'notify=["bash","{{NOTIFY_PATH}}"]' "$@"
+"$REAL_BIN" --enable codex_hooks "$@"
```

`hooks.json` の Stop hook を唯一の完了通知ソースとし、重複配送を根本から止めます。wrapper 内の TUI session log watcher(prompt 開始・権限要求の検知)は引き続き必要なので残しています。

2. 通知系初期化を `app.whenReady` 直後に1回だけ走らせる

`apps/desktop/src/main/windows/main.ts` から通知系初期化を `initNotifications()` として切り出し、`apps/desktop/src/main/index.ts` の `app.whenReady` 後で1回だけ呼ぶように変更しました。`notificationsInitialized` フラグで冪等性を担保しています。

`MainWindow()` は BrowserWindow の生成のみを担当するようにし、ウィンドウ再生成時にリスナーが多重登録される経路を構造的に排除しました。

やらなかったこと(意図)

入口での dedupe Map による短時間の重複抑制は採用しません。ユーザー側が「複数エージェントがたまたま同時に終了して2回鳴る」という正規の並列完了を許容しているため、dedupe を入れると意図的な連続完了まで抑えてしまい、症状の隠蔽で別バグを生む可能性があるためです。重複ソースを根本から1本化する方針で対応しています。

動作確認ポイント

  • Codex セッション終了時に aivis が1回だけ鳴ること
  • Claude Code / OpenCode など他エージェントの完了通知が従来どおり1回鳴ること
  • ウィンドウを × で閉じて Dock から再表示した後も、通知音・aivis が正常に発火すること

Summary by CodeRabbit

リリースノート

  • バグ修正

    • 通知システムの初期化タイミングを改善し、より早期にセットアップされるようになりました。
    • Codex統合で重複した完了通知を削除し、より効率的なフックライフサイクル管理を実現しました。
  • 削除

    • ワークスペース音声・動画ファイル視聴機能を削除しました。

## 原因

Codex の完了通知経路が2本存在し、1回のタスク完了に対して
/hook/complete が2回叩かれていたため、notificationsEmitter から
AGENT_LIFECYCLE が2回 emit され aivis が二重に再生されていた。

- ~/.codex/hooks.json の Stop hook
- Codex wrapper の `--enable codex_hooks -c 'notify=[...]'` オプション

## 修正

1. Codex wrapper 側の native `notify=` 注入を除去し、
   hooks.json を唯一の完了通知ソースに一本化。

2. NotificationManager / notificationsApp.listen /
   notificationsEmitter.on(AGENT_LIFECYCLE, ...) の初期化を
   MainWindow() の外に出し、app.whenReady 直後に initNotifications() で
   1回だけ実行するよう整理。ウィンドウ再生成経路でもリスナーが
   多重登録されない構造にしておく(将来の再発防止)。

通知音 / aivis いずれも「1回の完了 = 1回」で鳴るようになる。
正規の並列完了(複数エージェントがたまたま同時終了)は許容する方針なので、
dedupe は入れない。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

Warning

Rate limit exceeded

@MocA-Love has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 9 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 9 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 183a1e17-2b02-4e76-9c7b-df5cd1ffd701

📥 Commits

Reviewing files that changed from the base of the PR and between d024a36 and 6e6a0ff.

📒 Files selected for processing (5)
  • apps/desktop/docs/EXTERNAL_FILES.md
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
  • apps/desktop/src/main/windows/main.ts
📝 Walkthrough

Walkthrough

デスクトップアプリケーションの通知システムを再構成し、初期化タイミングを早期化しました。同時にCodexラッパーの実装を簡素化し、hooks.jsonベースの設定を優先するよう変更されました。

Changes

Cohort / File(s) Summary
Notification System Initialization
apps/desktop/src/main/index.ts, apps/desktop/src/main/windows/main.ts
通知システムの初期化をMainWindow()からinitNotifications()関数に分離し、app起動時に早期実行。モジュールレベルのライフサイクルリスナー登録とクリーンアップ処理を追加。superset-workspace-mediaプロトコルハンドラーを削除。
Codex Wrapper Simplification
apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts, apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts, apps/desktop/src/main/lib/agent-setup/templates/codex-wrapper-exec.template.sh
Codexラッパーの実装を簡素化。明示的なnotify=[...]フック設定を削除し、~/.codex/hooks.jsonベースの設定を優先。ドキュメントおよびテスト期待値を更新。

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main Process
    participant NotifMgr as NotificationManager
    participant NotifSrv as Notifications Server
    participant Lifecycle as Agent Lifecycle
    participant Terminal as Terminal Exit

    Main->>NotifMgr: initNotifications()
    activate NotifMgr
    NotifMgr->>NotifSrv: Create & Listen
    activate NotifSrv
    NotifMgr->>Lifecycle: Register AGENT_LIFECYCLE listener
    NotifMgr->>Terminal: Register terminalExit listener
    deactivate NotifMgr
    
    Lifecycle-->>NotifMgr: AGENT_LIFECYCLE event
    Terminal-->>NotifMgr: TERMINAL_EXIT event
    NotifMgr-->>NotifSrv: Emit notifications
    
    Main->>NotifMgr: cleanupMainWindowResources()
    activate NotifMgr
    NotifMgr->>Lifecycle: Unregister listener
    NotifMgr->>Terminal: Unregister listener
    NotifMgr->>NotifSrv: Close
    deactivate NotifMgr
    deactivate NotifSrv
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 通知たちを整理して、
ライフサイクルをきれいに並べて、
Codexラッパーは軽く、
hooks.jsonに任せて、
アプリケーションはスムーズに踊る ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive 提供されたテンプレートの必須セクション(Description、Related Issues、Type of Change、Testing)が不足しており、著者が独自形式(概要、原因、修正内容)で説明していますが、変更内容の詳細な技術説明は充実しています。 テンプレートの各セクション(Description、Related Issues、Type of Change、Testing)に従って記入し、Related Issues で関連イシューをリンクし、Type of Change で該当する項目をチェック、Testing で検証方法を明記してください。
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは日本語で、修正内容「aivis タスク完了通知が二重に読み上げられる問題を修正」を明確に要約しており、変更セットの主な目的と一致しています。

✏️ 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 fix/aivis-duplicate-completion-notification

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.

Copy link
Copy Markdown

@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: d024a36716

ℹ️ 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 thread apps/desktop/src/main/index.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts (1)

175-200: ⚠️ Potential issue | 🟡 Minor

テスト名から completion notifications in wrapper を外してください。

Line 200 の期待値は正しいですが、このテスト名はまだ wrapper が完了通知を注入するように読めます。完了通知は hooks.json 側に一本化されたので、watcher と codex_hooks 有効化の検証に寄せると混乱が減ります。

🧪 修正案
-	it("injects codex start + permission watchers and completion notifications in wrapper", () => {
+	it("injects codex start + permission watchers and enables native hooks", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts` around lines
175 - 200, Rename the test description string for the `it(...)` spec that calls
`createCodexWrapper()` so it no longer claims to verify "completion
notifications in wrapper" — update the test title to reflect that it only
verifies codex start and permission watchers and the `codex_hooks` enablement
(for example: "injects codex start + permission watchers in wrapper"); keep all
existing assertions unchanged (they reference things like `createCodexWrapper`,
the wrapper file content checks such as `_superset_emit_event`,
`_superset_approval_fallback_seq`, `"$REAL_BIN" --enable codex_hooks "$@"`, and
the various awk/printf checks).
apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts (1)

425-434: ⚠️ Potential issue | 🟡 Minor

fallback 表現が新しい方針と矛盾しています。

同じコメント内で Line 427 は hooks.json を fallback と呼んでいますが、Line 433-434 では primary source と説明しています。PR 方針に合わせて primary に統一してください。

📝 修正案
 /**
  * Writes Superset hook definitions directly into ~/.codex/hooks.json.
- * This provides a fallback notification path that works even when the
- * binary wrapper is not in PATH (e.g. user runs codex from outside
- * a Superset terminal).
+ * This provides the primary Codex lifecycle notification path. It also works
+ * when the binary wrapper is not in PATH (e.g. user runs codex from outside
+ * a Superset terminal).
  *
  * The wrapper now only enables Codex hooks and keeps the session-log watcher
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts`
around lines 425 - 434, The comment block is self-contradictory: it calls
~/.codex/hooks.json a "fallback" but later calls native hooks.json registration
the "primary lifecycle source"; update the comment so it consistently states
that hooks.json is the primary registration mechanism—replace the word
"fallback" (near the phrase "Writes Superset hook definitions directly into
~/.codex/hooks.json.") with language indicating it is the primary
registration/source, and tweak the following sentences (the one starting "The
wrapper now only enables Codex hooks..." and the one "Native hooks.json
registration remains the primary lifecycle source...") to read consistently
(e.g., state that the wrapper provides a best-effort bridge while native
hooks.json registration is the primary lifecycle source).
apps/desktop/src/main/index.ts (1)

523-558: ⚠️ Potential issue | 🔴 Critical

カスタムメディアプロトコルの登録が不足しています。

superset-workspace-media スキームが完全に未登録のため、FileViewerContent.tsx で使用される workspace ファイルの音声・動画再生が失敗します。同時に superset-temp-audiostream: true 特権が欠落しており、AudioEditor での再生とシーク機能が正常に動作しません。Electron の custom protocol でメディア再生を行うには、stream: true 特権の設定が必須です。

以下の修正が必要です:

  1. workspace-media-protocol.ts の handler を再度登録し、スキーム特権に stream: true を追加
  2. superset-temp-audio のスキーム特権に stream: true を追加
修正例
 import { createTempAudioProtocolHandler } from "./lib/temp-audio-protocol";
+import { createWorkspaceMediaProtocolHandler } from "./lib/workspace-media-protocol";
@@
 	{
 		scheme: "superset-temp-audio",
 		privileges: {
 			standard: true,
 			secure: true,
 			bypassCSP: true,
 			supportFetchAPI: true,
+			stream: true,
 		},
 	},
+	{
+		scheme: "superset-workspace-media",
+		privileges: {
+			standard: true,
+			secure: true,
+			bypassCSP: true,
+			supportFetchAPI: true,
+			stream: true,
+		},
+	},
@@
 		const tempAudioHandler = createTempAudioProtocolHandler();
 		protocol.handle("superset-temp-audio", tempAudioHandler);
 		session
 			.fromPartition("persist:superset")
 			.protocol.handle("superset-temp-audio", tempAudioHandler);
+
+		const workspaceMediaHandler = createWorkspaceMediaProtocolHandler();
+		protocol.handle("superset-workspace-media", workspaceMediaHandler);
+		session
+			.fromPartition("persist:superset")
+			.protocol.handle("superset-workspace-media", workspaceMediaHandler);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/index.ts` around lines 523 - 558, The custom media
protocol registration is missing `superset-workspace-media` and
`superset-temp-audio` lacks the required `stream: true` privilege, causing
workspace media playback and AudioEditor seek to fail; fix by adding a
registration entry for the `superset-workspace-media` scheme in the
protocol.registerSchemesAsPrivileged array and ensure both `superset-temp-audio`
and `superset-workspace-media` privilege objects include "stream: true", and
re-register the handler from workspace-media-protocol.ts (the handler
registration call used previously) so the workspace-media handler is active when
FileViewerContent.tsx and AudioEditor request media.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/index.ts`:
- Around line 760-761: Move the call to initNotifications() so it runs
immediately after app.whenReady() instead of after setupAgentHooks() and other
awaits; locate the app.whenReady() initiation and call initNotifications() there
(before invoking setupAgentHooks(), before awaiting makeAppSetup(() =>
MainWindow()), and before any async setup that could delay the notification
server/listener), ensuring notification server/listener is initialized early to
avoid missing /hook/complete events from Codex.

In
`@apps/desktop/src/main/lib/agent-setup/templates/codex-wrapper-exec.template.sh`:
- Line 75: ドキュメントの説明が実装と矛盾しているため、apps/desktop/docs/EXTERNAL_FILES.md の「wrapper が
notify を inject する」と書かれた記述を削除または更新し、現在の実装(wrapper が "$REAL_BIN" --enable
codex_hooks "$@" を呼び出すだけで `notify=[...]` を注入しない)を明示してください;具体的には
EXTERNAL_FILES.md 内の notify に関する節を見つけて、inject を前提とした手順やサンプルを修正し、notify
を渡す方法(呼び出し側で明示的に引数として渡す等)を短く追記してください。

---

Outside diff comments:
In `@apps/desktop/src/main/index.ts`:
- Around line 523-558: The custom media protocol registration is missing
`superset-workspace-media` and `superset-temp-audio` lacks the required `stream:
true` privilege, causing workspace media playback and AudioEditor seek to fail;
fix by adding a registration entry for the `superset-workspace-media` scheme in
the protocol.registerSchemesAsPrivileged array and ensure both
`superset-temp-audio` and `superset-workspace-media` privilege objects include
"stream: true", and re-register the handler from workspace-media-protocol.ts
(the handler registration call used previously) so the workspace-media handler
is active when FileViewerContent.tsx and AudioEditor request media.

In
`@apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts`:
- Around line 425-434: The comment block is self-contradictory: it calls
~/.codex/hooks.json a "fallback" but later calls native hooks.json registration
the "primary lifecycle source"; update the comment so it consistently states
that hooks.json is the primary registration mechanism—replace the word
"fallback" (near the phrase "Writes Superset hook definitions directly into
~/.codex/hooks.json.") with language indicating it is the primary
registration/source, and tweak the following sentences (the one starting "The
wrapper now only enables Codex hooks..." and the one "Native hooks.json
registration remains the primary lifecycle source...") to read consistently
(e.g., state that the wrapper provides a best-effort bridge while native
hooks.json registration is the primary lifecycle source).

In `@apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts`:
- Around line 175-200: Rename the test description string for the `it(...)` spec
that calls `createCodexWrapper()` so it no longer claims to verify "completion
notifications in wrapper" — update the test title to reflect that it only
verifies codex start and permission watchers and the `codex_hooks` enablement
(for example: "injects codex start + permission watchers in wrapper"); keep all
existing assertions unchanged (they reference things like `createCodexWrapper`,
the wrapper file content checks such as `_superset_emit_event`,
`_superset_approval_fallback_seq`, `"$REAL_BIN" --enable codex_hooks "$@"`, and
the various awk/printf checks).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5215e15-8869-4124-a1cf-10b05413fc7e

📥 Commits

Reviewing files that changed from the base of the PR and between 5dcb03b and d024a36.

📒 Files selected for processing (5)
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
  • apps/desktop/src/main/lib/agent-setup/templates/codex-wrapper-exec.template.sh
  • apps/desktop/src/main/windows/main.ts

Comment thread apps/desktop/src/main/index.ts
- index.ts の workspace-media-protocol 登録と stream 特権を誤って削除していたのを復旧 (CodeRabbit critical 指摘)
- createCodexHooksJson のコメントを primary/fallback で矛盾しないよう修正
- codex wrapper テスト名から completion notifications 表記を除去
- biome フォーマット修正 (main.ts / agent-wrappers.test.ts)
@MocA-Love MocA-Love merged commit b487718 into main Apr 18, 2026
6 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