agent-notifications 在 Windows 上发送通知会闪一个控制台窗口
之前也记过一次类似的控制台闪窗,当时是 tokentracker-cli(时不时闪现控制台窗口)。这次轮到 777genius/agent-notifications 了。通知弹出来时会闪出控制台窗口,Codex 只是触发 hook 的调用方,问题在通知程序的 Windows 启动链上。
我一开始也留意了通知程序的 -focus.exe。后来确认它是点 toast 后用来聚焦窗口的;真正发送通知的 claude-notifications-windows-amd64.exe 还是 Console 子系统程序。旧的 Windows hook 用 cmd.exe 启动 .cmd wrapper,hook 一运行就会带出控制台。PR #120 修的是点击 toast 后的聚焦路径,所以对通知出现时的闪屏没帮助。PR #120
处理时我让 pwsh.exe 隐藏启动 PowerShell wrapper,再由 wrapper 用 ProcessStartInfo 无窗口启动通知程序。完整脚本留在这里,之后要回看就不用再翻配置目录:
$ErrorActionPreference = 'Stop'
$hookArguments = @($args)
function New-HiddenProcessStartInfo {
param(
[Parameter(Mandatory)]
[string]$FilePath,
[string[]]$Arguments = @()
)
$startInfo = [System.Diagnostics.ProcessStartInfo]::new()
$startInfo.FileName = $FilePath
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true
foreach ($argument in $Arguments) {
$startInfo.ArgumentList.Add($argument)
}
return $startInfo
}
function Invoke-CodexHook {
param(
[Parameter(Mandatory)][string]$BinaryPath,
[Parameter(Mandatory)][string[]]$Arguments
)
$startInfo = New-HiddenProcessStartInfo -FilePath $BinaryPath -Arguments $Arguments
$startInfo.RedirectStandardInput = $true
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$process = [System.Diagnostics.Process]::Start($startInfo)
try {
$null = $process.StandardOutput.ReadToEndAsync()
$null = $process.StandardError.ReadToEndAsync()
try {
[Console]::OpenStandardInput().CopyTo($process.StandardInput.BaseStream)
}
finally {
$process.StandardInput.Close()
}
if (-not $process.WaitForExit(24000)) {
$process.Kill()
$process.WaitForExit()
}
}
finally {
if (-not $process.HasExited) {
$process.Kill()
$process.WaitForExit()
}
$process.Dispose()
}
}
try {
$env:CN_PRODUCT = 'codex'
if ([string]::IsNullOrEmpty($env:PLUGIN_ROOT)) {
$env:PLUGIN_ROOT = [System.IO.Directory]::GetParent($PSScriptRoot).FullName
}
$binary = Join-Path $PSScriptRoot 'claude-notifications-windows-amd64.exe'
# Codex product routing requires claude-notifications v1.42.0 or newer.
if (Test-Path -LiteralPath $binary -PathType Leaf) {
Invoke-CodexHook -BinaryPath $binary -Arguments $hookArguments
}
}
catch {
# Codex observation hooks must remain silent and never block the session.
}
exit 0wrapper 会把 hook 的标准输入传给通知程序,传完关闭输入流并等待进程退出。只给 PowerShell 加 -WindowStyle Hidden 还不够,真正创建通知进程时也得设 CreateNoWindow。
hooks.json 里四个 Windows hook 都指向这个 wrapper,只是传入的事件名不同;PreToolUse 还限制了 matcher:
PermissionRequest:
pwsh.exe -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Users\enihsyou\.codex\claude-notifications-go\bin\codex-hook-wrapper.ps1" handle-hook PermissionRequest --product codex
PreToolUse (matcher: ^request_user_input$):
pwsh.exe -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Users\enihsyou\.codex\claude-notifications-go\bin\codex-hook-wrapper.ps1" handle-hook PreToolUse --product codex
Stop:
pwsh.exe -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Users\enihsyou\.codex\claude-notifications-go\bin\codex-hook-wrapper.ps1" handle-hook Stop --product codex
SubagentStop:
pwsh.exe -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Users\enihsyou\.codex\claude-notifications-go\bin\codex-hook-wrapper.ps1" handle-hook SubagentStop --product codex最后我用实际 hook 验证通过了:通知正常送达,控制台也不再闪。
2026-10-17 发现 v1.49.4 添加了修复 fix(notifier): start Windows toast PowerShell without a console window by harshbhx · Pull Request #482 · 777genius/agent-notifications 暂时未测试,因为它假设 Windows 上 PATH 中一定有 sh,崩溃了
千问语音输入法在 Codex 中会丢失重复的字母
我在 Codex CLI 0.159.2 中使用千问输入法的语音输入时,输入框遇到语音输入词中有两个双写字母时会被删掉一个,比如 Git commit 会只出现 Git comit。
测试会发生的环境有
- Windows Terminal + PowerShell + 千问语音输入法 + Codex = 丢字母
- Windows Terminal + PowerShell + 千问语音输入法 = Boom
- Windows Terminal + PowerShell + 智谱 AI 输入法 + Codex = Error
- Windows Terminal + Git Bash + 千问语音输入法 = 🆗
- VSCode + PowerShell + 千问语音输入法 = 🆗
- VSCode + PowerShell + 千问语音输入法 + Codex = 🆗
- VSCode + PowerShell + 智谱 AI 输入法 + Codex = 🆗
PowerShell 的 Boom 信息是这样,因为我把右 Ctrl 绑定到了语音输入激活键,当我松开时就会触发这个
发生了错误且未正确处理。下面显示附加信息。PowerShell 进程将退出。
Unhandled exception. System.ArgumentOutOfRangeException: Console key values must be between 0 and 255 inclusive. (Parameter 'key')
at System.ConsoleKeyInfo..ctor(Char keyChar, ConsoleKey key, Boolean shift, Boolean alt, Boolean control)
at System.ConsolePal.ReadKey(Boolean intercept)
at Microsoft.PowerShell.Internal.VirtualTerminal.<>c.<ReadKey>b__40_0()
at Microsoft.PowerShell.Internal.VirtualTerminal._TryIgnoreIOE[T](Func`1 f)
at Microsoft.PowerShell.Internal.VirtualTerminal.ReadKey()
at Microsoft.PowerShell.PSConsoleReadLine.ReadOneOrMoreKeys()
at Microsoft.PowerShell.PSConsoleReadLine.ReadKeyThreadProc()
at System.Threading.Thread.StartHelper.Callback(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
[已退出进程,代码为 3762504530 (0xe0434352)]
现在可以使用Ctrl+D关闭此终端,或按 Enter 重新启动。但实测改成其他键都不行,GPT 分析是 PSReadLine 的问题
在 Windows PowerShell 中 Remove-Module PSReadLine 后确实正常了,但对 PowerShell 7 不管用。
智谱遇到的 Error 信息是这样的,内容实际上已经到剪贴板了,可以再按 Ctrl+Shift+V 粘贴进去
■ Failed to paste image: no image on clipboard: The clipboard contents were not available in the requested format or the clipboard is empty.GPT 还建议给 Codex 添加 tui.disable_paste_burst = true,实测不行。
所以很明显问题出现在 Windows Terminal 处理语音输入的时候。
但排查半天测试来测试去,即便用 PowerToys 重映射按键也不行。最终原因是这样:
我之前一直在用 Rime 输入法,它是我的默认输入法。但千问语音输入法其实也是一个系统级别的输入法,可以按 Win + 空格切换的。
在我把 Rime 小狼毫设为默认输入法,并关闭千问输入法中“语音输入”中“在语音输入时自动切换为千问输入法”选项的情况下,按右 Ctrl 激活语音输入,其他应用一切正常,可以弹出语音输入界面。但在终端场景下,就出现了上述问题。
当我用 Win + 空格把主输入法切换到千问时,一切就正常了 🤨 真是鱼和熊掌不可兼得
让 Codex 会话知道自己是什么模型
我比较认同模型写的代码就应该以模型的身份进行提交,所以之前做了个 使用 GitHub App 以 Bot 身份创建属于 AI Agent 的提交 和 ai-commit SKILL 用于模型主动进行 git commit。
在 Claude Code 里运行得很好,因为 Claude Code 的系统提示词会把当前使用的模型信息注入进去;但在 Codex 里,它的 models_cache.json 文件的 models[].model_messages.instructions_template 只会提到 You are Codex, an agent based on GPT-6. 并不包含具体的模型名称,那么想让模型在执行 AI Commit 的 Skill 时知道自己是谁就成了一个问题,我做过好几种解决办法。
Codex 有 Hooks,它的脚本接收的参数里面有一个 model 这个参数,对应当前模型的 slug。我当时的想法是新建一个 Session Start 的 Hook,然后把模型信息添加到 Additional Context 里面。
当时工作得很好,但是在 Codex Desktop 界面中它会显示你运行了一个 Hook。我觉得不是很好看,就换成下一个方案了
在使用 Codex & Claude Code Session Viewer — SessionViewer 查看 Codex 的会话记录时,我发现系统提示词中会有一段来自 visualizations 的路径,刚好记录了主 Agent 的 Session ID。询问模型,它能从上下文中获得哪些信息,也确认了这一点,能够模型能够自主地根据 Session ID 去寻找 Rollout 文件,从中使用 JQ 过滤出最后一个消息所使用的模型,甚至还包括 Effort。
<environment_context>
<cwd>D:\GitHub\subconverter</cwd>
<shell>powershell</shell>
<current_date>2026-08-30</current_date>
<timezone>Asia/Shanghai</timezone>
<filesystem>
<permission_profile type="managed">
<file_system type="restricted">
<entry access="write">
<path>D:\GitHub\subconverter</path>
</entry>
<entry access="write">
<path>C:\Users\enihsyou\.codex\visualizations\2026\08\30\01a0535f-997c-77f1-b696-5b790cbabfbe</path>
</entry>
</file_system>
</permission_profile>
</filesystem>
</environment_context>所以我就调整了 AI Commit 指令,让模型从上下文中寻找 Session ID,然后经过一段 Tool Call 取得当前适用的模型信息,这样就摆脱了 Hooks。
但问题是我最近把 Visualization 这个插件卸载了,又没找到入口重装回来(好像每次更新 Codex 它都会自动重装回来)。这个 WritableRoot 的 pass 就不在上下文中出现了,那这条路突然就不通了。
所以最后我还是回归了第一条路,编写 hooks。因为用了 Agents Notification 这个给 CLI 完成做通知的小工具,它是基于 Hooks 完成的,所以现在对使用 Hooks 没有什么阻力。
但是这次除了给模型 model id 的信息,还把它的 rollout 文件路径也加到 context 中,并给了更详细的伪代码指令,先以传入的 model slug 为主,但如果你是个 subagent,就去使用 rollout 文件路径寻找主 agent 的 model slug。