Skip to content

[Bug] Windows: a single unanalyzable command permanently disables shell in that session (non-POSIX working directory requires a shell path adapter) #3068

Description

@Cid-Nova

EN TL;DR — On Windows, after one command the guard cannot analyze, every subsequent ExecCommand in that session fails with non-POSIX working directory requires a shell path adapter, permanently (it survives an app restart). New sessions and subagents are unaffected. The same command runs fine elsewhere, so the deny is scoped to the session, not to the command. Fail-closed at session scope, with no user-visible notice.


环境

  • OpenBitFun 1.0.0 桌面版(D:\Openbitfun\openbitfun-desktop.exe,2026-09-15 安装)
  • OS:Windows 10 专业版 22H2(10.0.19045,AMD64)
  • Shell:Windows PowerShell 5.1.19041.6456
  • 工作区:C:\Users\admin\.openbitfun\personal_assistant\workspace
  • 模型:DeepSeek V4.1 Flash(官方 API),reasoning 档位 high

现象

同一会话内,ExecCommand 起初正常可用;中途一次调用后,后续所有 ExecCommand 全部失败,报错:

Validation error: Command was not executed. Shell analysis could not confirm this operation: non-POSIX working directory requires a shell path adapter. Use explicit paths and inspectable execution input; invalid or unsupported syntax must be corrected.
Shell guard details: {"analysis_status":"unsupported","blocks_input_rewrite":true,"constraint_id":null,
"effective_workdir":"C:\\\\Users\\\\admin\\\\.openbitfun\\\\personal_assistant\\\\workspace","executed":false,
"failure_kind":"edit_constraint_guard","force_requested":false,"guard_decision":"deny_shell_analysis",
"operation":null,"protected_path":null,"reason":"non-POSIX working directory requires a shell path adapter"}

此后连最无害的 Get-Location 也一样被拒。

复现步骤

  1. 新会话中正常执行若干命令。本例前 20 分钟内:Test-Path、Select-String、Copy-Item、python -X utf8 xxx.py、git commit 全部成功

  2. 执行一条守卫无法分析的命令。本例触发命令为:

    gh api repos/GCWing/OpenBitFun --jq '{full_name:.full_name, name:.name}'

    (特征:外部 exe + 单引号包裹 + 花括号语法)

  3. 此后再执行任意 ExecCommand(含 Get-Location)→ 全部被拒,不再恢复

关键观察(对照实验)

通道 结果
该会话的 ExecCommand ❌ 全拒,持续失效
重启应用(新进程 pid)后,同一会话 ❌ 仍然全拒(重启后第一条命令就被拒)
新建会话的 ExecCommand ✅ 正常
子代理(Task → GeneralPurpose)的 ExecCommand ✅ 正常(同一命令、同一工作目录、同一进程)

结论:拒绝状态是会话级持久的,跨进程重启保留;不波及新会话与子代理。

为什么这不是"命令本身无法分析"

这是本 issue 最关键的一点:

  • 被拒的 Get-Location 是零歧义命令,不存在"无法分析"的可能
  • 完全相同的命令、相同工作目录、相同进程,交给子代理执行 → 成功通过
  • 因此判定的对象不是命令,而是该会话的 shell 环境;"non-POSIX working directory requires a shell path adapter" 只是兜底的失败原因文案

也就是说:一旦某个会话被守卫标记,它的 shell 就被整体关掉了,与该会话后续发出什么命令无关。

影响

  • 会话中途永久失去 shell 能力(python / git / 命令行工具全废),只能改用文件工具或另开会话 —— 长任务被迫中断
  • 全程没有任何用户可见提示:用户和模型都不知道该会话已经"残废",只会看到连续的验证失败
  • Windows 路径是常态,非 POSIX 工作目录本不该成为"无法分析"的理由。疑似缺少 Windows 路径适配层(app.json 中 terminal.default_shell 为空是否相关?未验证)

期望行为

  1. 单条命令分析失败 → 只拒绝该条,不应连坐整个会话
  2. 若确实需要锁定,请在 UI 明确告知原因,并提供解锁/重置方式(当前唯一出路是另开会话,用户无从得知)
  3. Windows 非 POSIX 工作目录应能正常参与命令分析(补齐 path adapter)
  4. 行为一致性:如果环境确实缺少 path adapter,应在会话开始时就一致地禁用并提示,而不是先正常工作 20 分钟再突然锁死

附:定位信息

  • 日志:%APPDATA%\openbitfun\logs\<时间戳>\app.log,来源模块 openbitfun_core::agentic::tools::pipeline::tool_pipeline
  • 触发时刻(本地 2026-09-16):首次 20:04:50;20:08 重启,20:09 同一会话仍全拒
  • 同守卫体系的另一可见症状:工具写工作区外文件时(如 ~/.openbitfun/memories/)报警告 Git isolation verification failed: file path is not safe
  • 相关配置现状:app.json → terminal.default_shell: ""、tool_permissions.policy.preset: "ask"、tool_permissions.interaction.auto_approve_ask: true;projects/<workspace>/config/isolation_status.json → git_isolated: true

Activity

  1. Cid-Nova commented on Sep 16, 2026

    @Cid-Nova
    Author

    补充:在会话状态文件里发现了该守卫的实现线索

    后续检查本机会话状态时,在 sessions/<id>/metadata.json 的 customMetadata.editConstraintGuard 里看到这个守卫的结构。它看起来是模型驱动的,这大概能解释为什么会"突然锁死且重启不恢复":

    "editConstraintGuard": {
      "constraints": [
        {
          "description": "Keep the AGENTS.md entry as originally written (do not change it)",
          "id": "model:c5eb69895eb7:0",
          "matcher": { "kind": "path_contains", "substrings": ["AGENTS.md"] },
          "operation_scope": "all",
          "source": "model"
        }
      ],
      "extractions": [
        {
          "dialog_turn_id": "dialog_1789560050732_m5i0rhfcd",
          "extracted_at_ms": 1789560052143,
          "latency_ms": 1070,
          "model_attempts": 1,
          "model_status": "parsed",
          "status": "no_constraints",
          "response_excerpt": "{\"additions\": [], \"revocations\": []}"
        }
      ]
    }

    观察到几点:

    1. 约束是从对话里用模型抽取出来的:用户在某一轮说"这条内容要保留原貌",被抽取成一条 path_contains: AGENTS.md 的禁改约束,且 source: "model"(标明由模型生成),随后用于校验工具操作。
    2. 校验过程带模型调用:extractions[] 中记录了 latency_ms(例子中 1070 ms / 4679 ms 各一次)、model_attempts、model_status,说明每次判定可能依赖一次模型分析。
    3. 因此 "analysis_status": "unsupported" 很可能表示模型分析环节没有跑通(超时、解析失败或返回不可用),而不是 shell 语法无法解析。这与前面的对照实验一致:同一条 Get-Location 交给子代理就能正常执行。
    4. customMetadata 挂在会话上,与"锁死状态跨应用重启保留、但不影响新会话与子代理"的现象完全吻合。

    如果 "non-POSIX working directory requires a shell path adapter" 是这条模型分析链在 Windows 路径下的兜底失败原因,那么修复方向可能是:

    • 分析无法完成时降级为只拒绝该条命令,而不是把整个会话的 shell 标记为不可用;
    • 或者至少在 UI 明确提示该会话的 shell 已被禁用,并给出恢复/重置方式。

    以上仅是从客户端状态文件观察到的线索,未必与实现一一对应,供定位参考。

  2. Cid-Nova commented on Sep 16, 2026

    @Cid-Nova
    Author

    补充证据:约束被抽出的 9 秒后,该会话的命令即开始被拒

    按上一条评论的线索继续核对会话状态,拿到了时间戳证据,因果关系基本可以坐实。

    customMetadata.editConstraintGuard.extractions[] 里一共只有两条记录:

    # 抽取时间(本地) 抽取结果 说明
    1 20:00:52 status: "no_constraints" 未抽到任何约束;此后约 20 分钟内多条命令(Copy-Item / python / git commit 等)全部成功
    2 20:04:41 status: "extracted" 抽出 1 条约束(用户在对话中要求某条内容保持原样)

    抽出的约束内容:

    {
      "id": "model:c5eb69895eb7:0",
      "description": "Keep the AGENTS.md entry as originally written (do not change it)",
      "matcher": { "kind": "path_contains", "substrings": ["AGENTS.md"] },
      "operation_scope": "all",
      "source": "model"
    }

    而同一会话首次出现守卫拒绝的时间是 20:04:50.307(app.log 第 4694 行,模块 openbitfun_core::agentic::tools::pipeline::tool_pipeline,报错内容即本 issue 正文那条)。

    也就是说:约束被抽出后 9 秒,该会话的第一个 ExecCommand 就被拒,此后持续失效。

    由此可以推出

    1. 会话内没有约束时:工具执行无约束可对照,命令正常放行(与本例 19:51–20:04 期间的表现一致,当时 constraints 为空)
    2. 一旦抽取到约束:此后每次工具执行都需要"对照约束做分析",而这条分析链在非 POSIX(Windows)工作目录下无法完成 → analysis_status: "unsupported" → 报 non-POSIX working directory requires a shell path adapter
    3. 该失败是 fail-closed,并被持久化到会话状态 → 该会话内所有 ExecCommand 被永久拒绝(跨应用重启保留)
    4. 新会话与子代理没有约束 → 一切正常(与上一条评论的对照实验一致)

    影响定性(比"偶发锁死"更严重)

    在 Windows 上,只要模型从对话中抽到任意一条约束,该会话的 shell 就基本不可用了。而"用户要求某条内容不要改动"是极常见的交互——这意味着该功能在当前 Windows 版本上从"保护机制"直接变成"瘫痪机制",并且全程没有任何用户可见提示。

    建议排查方向:

    • 该分析链在非 POSIX 工作目录下的路径适配:shell path adapter 由什么提供?与 terminal.default_shell 为空是否相关?
    • 分析失败时的降级策略:应只拒绝该条命令(或仅拒绝该约束覆盖的操作范围),而不是禁用整个会话的 shell
    • UI 至少应告知用户"该会话已进入受限状态",并提供恢复方式

    以上时间戳与字段均来自本机客户端状态文件与日志,可逐项复现核对。

  3. ManyueEureka commented on Sep 16, 2026

    @ManyueEureka

    该bug在win11、pwsh7上也有出现。EN TL;DR — Confirmed on 1.0.0 stable as well (not beta-only). Reproduces on pwsh 7.6.6 (Core) / Windows 11 build 26220, so it is neither shell-specific nor Win10-specific. Found a strong discriminator: the failing session is the only one whose workspace has config/isolation_status.json (git_isolated: true) — which also explains why "new sessions and subagents work fine". One negative result too: terminal.default_shell is not the cause.


    1. 复现数据

    项 本例 原报告
    应用版本 1.0.0-beta → 升级到 1.0.0 后仍复现(同 payload,仅 guard_decision_id 不同) 1.0.0
    Shell pwsh 7.6.6 (PSEdition Core) Windows PowerShell 5.1.19041.6456
    OS Windows 11, build 26220 Windows 10 22H2, build 19045
    effective_workdir …\.openbitfun\personal_assistant\workspace(无 hash 后缀) 同形状

    两个 shell、两个 Windows 版本均复现 → 可排除"pwsh 特有"与"Win10 特有"两条假设。

    2. 完整 guard payload

    (原报告的粘贴在 force_requested 处截断,此处补全)

    {"analysis_status":"unsupported","blocks_input_rewrite":true,"constraint_id":null,
     "effective_workdir":"<personal_assistant workspace>",
     "executed":false,"failure_kind":"edit_constraint_guard","force_requested":false,
     "guard_decision":"deny_shell_analysis","guard_decision_id":"05766c8d-c869-427b-8e05-b963791cbb93",
     "operation":null,"protected_path":null,
     "reason":"non-POSIX working directory requires a shell path adapter",
     "requested_target":null,"resolved_target":null,"shell_kind":"pwsh",
     "source_span":{"start":0,"end":0,"end_exclusive":true,"unit":"utf8_bytes"}}

    字段解读,支持原报告"判定对象不是命令"的结论:

    • constraint_id: null → 未命中任何具体约束,即这不是策略拒绝,而是通用兜底退出
    • operation / requested_target / resolved_target / protected_path 全为 null → 守卫未识别出操作与目标
    • source_span 为零宽(start == end == 0)→ 在产出任何定位信息之前就退出,说明"无法分析"发生在解析阶段而非约束匹配阶段
    • 三者合起来解释了这条消息为何对用户和模型都不可操作:拒绝时不携带任何可定位的证据

    3. 定位线索:判别条件可能是 isolation_status.json,而非 shell 或 OS

    同一安装下存在四个会话工作区。触发 deny 的那个是唯一带 projects/<workspace>/config/isolation_status.json 的:

    { "created_at": "…", "git_isolated": true, "version": "1.0" }
    工作区 isolation_status.json 目录内有 .git ExecCommand
    personal_assistant\workspace 有(git_isolated: true) 无 ❌ deny
    personal_assistant\workspace-<hash> 无 无 ✅ 实测正常
    另两个 workspace-<hash> 无 无 未逐一验证

    原报告环境同样有 isolation_status.json → git_isolated: true,两个独立案例同形状。

    据此可自洽解释"新会话正常 / 子代理正常":并非绕过了守卫,而是新会话与子代理落在没有该文件的工作区,该守卫根本未被触发。症状形似"会话被拉黑",实质更像"该工作区带隔离标记"。

    推测链条(未证明,仅作定位参考):

    工作区被标记 git_isolated: true,但目录内并无 .git → edit_constraint_guard 需经 shell 执行 git 来校验编辑是否越界 → 需按 cwd 解析路径 → Windows cwd + pwsh 缺 path adapter → 放弃解析 → fail-closed 拒绝。

    建议的最小验证:在带与不带该文件的两个工作区各执行同一条编辑命令,确认 deny 只在前者出现。

    4. 一条负结果:terminal.default_shell 不是原因

    原报告怀疑该项为空相关。本机数据可排除:

    • 迁移前(旧版本配置备份):"default_shell": ""
    • 当前配置:"default_shell" 为非空值,且仍然复现

    (该字段非空是由版本迁移过程写入的,说明正常路径下会被填上,但 deny 与之无关。)

    5. 未观察到的点(避免误导)

    • 未观察到跨会话扩散:同安装上另一会话(工作区无 isolation_status.json)ExecCommand 正常,与原报告"不波及新会话"一致。
    • 未确认触发命令的形态,因此无法就"外部 exe + 单引号 + 花括号"这一触发特征提供正反证。

    6. 期望行为追加一条(在原文 1–4 之外)

    1. deny 消息应携带可定位信息:至少给出命中的 constraint_id、非零宽 source_span,以及"为何需要 path adapter"的具体缺失项。当前 payload 相关字段全为 null + 零宽 span,等于要求用户"修正语法"却不指出问题在哪——模型会反复重试同一条命令而耗尽会话。
  4. self-assigned this
    on Sep 17, 2026
  5. Cid-Nova commented on Sep 17, 2026

    @Cid-Nova
    Author

    感谢 @wsp1911 的快速修复,也感谢 @ManyueEureka 的复现数据。

    Thanks @wsp1911 for the quick fix, and @ManyueEureka for the reproduction data.

  6. wsp1911 commented on Sep 17, 2026

    @wsp1911
    Collaborator

    感谢支持 OpenBitFun 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions