Skip to content

fix(cosh-ng): [shell] intercept zsh slash-bearing natural-language prompts - #2210

Open
SunnyQjm wants to merge 1 commit into
mainfrom
fix/cosh-1943-zsh-slash-nl-intercept
Open

fix(cosh-ng): [shell] intercept zsh slash-bearing natural-language prompts#2210
SunnyQjm wants to merge 1 commit into
mainfrom
fix/cosh-1943-zsh-slash-nl-intercept

Conversation

@SunnyQjm

@SunnyQjm SunnyQjm commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

zsh sibling of #1919 (bash fixed in #1942): a natural-language prompt whose
first IFS token contains a slash and does not resolve to an existing path is
executed by zsh as a path — zsh never invokes command_not_found_handler for
slash-bearing command words, so the NL classifier hooked there is unreachable
and the user sees a raw zsh: no such file or directory error instead of the
agent taking over. zsh's DEBUG trap has no veto-execution semantics (probe
evidence in #1943), so the bash mechanism cannot be ported; the only
pre-execution seam zsh offers is the accept-line ZLE widget.

Changes

  • marker/zsh.rs: new _cosh_should_intercept_missing_path — the bash
    missing-path verdict (slash-bearing first word, AI enabled, path provably
    ENOENT, missing-path classification says natural_language) plus a URL
    veto: scheme-shaped first words ([a-zA-Z]*://*) always keep the native
    result. The shared input_intent.sh classifier is reused verbatim
    (single source of truth; classifier and bash side have zero diff).

  • marker/zsh.rs: _cosh_accept_line ZLE widget mounted over the active
    accept-line (alias-saving chain keeps user widgets working) evaluates the
    gates at line submission. On a natural_language verdict it re-adds the line
    to history (print -sr; skipped for secret-bearing lines), re-echoes the
    prompt+text (ZLE erases the edit line on buffer clear; sensitive lines
    re-echo the redaction placeholder), emits the same intercept marker shape
    as the bash missing-path route (natural_language,
    top_level_missing=false, sensitive as-is), clears the buffer and
    accepts the empty line. Every gate failure and internal error falls open to
    the original accept-line: the worst case is the interception not firing,
    never a broken native line. CONTEXT != start submissions (PS2/heredoc
    continuations, vared) always pass through.

  • The zsh marker script body lives in zsh_marker.sh
    (include_str!, byte-identical move) keeping zsh.rs under the
    700-line layout gate; slash registry tests re-anchored to the script
    file.

Tests

Twenty-three zsh-side integration tests mirroring the bash anchors
(tests/shell_host/marker.rs):

  • shell_host_zsh_missing_path_natural_language_intercepts — positive
    intercept, no native error, re-echo visible.

  • shell_host_zsh_sensitive_missing_path_natural_language_intercepts
    sensitive flag, journal whole-field redaction, redaction placeholder
    re-echo, raw key absent from events/journal.

  • shell_host_zsh_missing_path_counterproofs_stay_native — existing
    executable, English typo path, dangling symlink, permission-opaque parent
    all keep native behavior; slash-free CNF route unaffected.

  • shell_host_zsh_missing_path_intercepts_with_user_accept_line_widget
    interception works with a user accept-line widget present and the user
    widget stays in the chain for pass-through lines.

  • shell_host_zsh_missing_path_heredoc_continuation_stays_native
    CONTEXT=cont lines pass through untouched.

  • shell_host_zsh_missing_path_intercepts_with_builtin_alias_accept_line
    zle -A builtin-alias customizations survive the mount and native lines
    keep executing through the preserved alias (review P2).

  • shell_host_zsh_missing_path_intercept_never_runs_user_widget_synthesis
    an empty-buffer-synthesizing user widget never executes a native command
    after a successful intercept (review round 2).

  • shell_host_zsh_missing_path_foreign_history_hook_stays_uninvoked
    a foreign fc -p zshaddhistory hook is never replayed and the session
    HISTFILE context stays untouched (review round 2).

  • shell_host_zsh_missing_path_url_first_word_stays_native — URL first
    word + NL text keeps the native error (review round 3).

  • shell_host_zsh_missing_path_intercepts_with_direct_submit_key_binding
    bindkey '^M'/'^J' straight to a user widget: interception reachable,
    pass-through keeps the user widget (review round 3).

  • shell_host_zsh_missing_path_intercepted_lines_stay_out_of_history
    repeated intercepted prompts leave zero history entries (review round 3).

  • shell_host_zsh_missing_path_intercept_does_not_reevaluate_ps1
    PROMPT_SUBST side effects run exactly as often as native submission
    (review round 3).

  • shell_host_zsh_missing_path_submit_key_widgets_stay_per_keymap
    distinct submit widgets in the main and vicmd keymaps never cross modes
    (review round 4).

  • shell_host_zsh_missing_path_reentrant_accept_line_runs_widget_once
    a delegated widget finishing with the named zle accept-line runs
    exactly once and the line still submits (review round 5).

  • shell_host_zsh_missing_path_slash_function_stays_native — a defined
    slash-bearing function executes natively; the gate mirrors zsh
    resolution via whence -w (review round 6).

  • shell_host_zsh_missing_path_han_glob_stays_native — a Han first word
    carrying an unquoted glob expands and runs natively (review round 6).

  • shell_host_zsh_missing_path_reentrant_reaches_saved_accept_line — a
    re-entrant named accept-line reaches the saved user wrapper, keeping
    the A → B chain intact (review round 6).

  • shell_host_zsh_missing_path_quoted_function_stays_native — a quoted
    slash-bearing function call runs natively; the literal-word whitelist
    fails open on any lexing/expansion form (review round 7).

  • shell_host_zsh_missing_path_brace_expansion_stays_native — a
    brace-expanded first word runs natively (review round 7).

  • shell_host_zsh_missing_path_aliased_keymap_keeps_user_widget — with
    bindkey -A main vicmd the claim scan mirrors saved entries so a
    vicmd submission still reaches the user's widget (review round 7).

  • shell_host_zsh_missing_path_custom_keymap_still_intercepts — a
    user-defined keymap selected from zle-line-init is claimed at mount;
    interception fires and the user's widget stays (review round 8).

  • shell_host_zsh_missing_path_space_keymap_still_intercepts — a
    space-bearing keymap name is enumerated per line and claimed
    (review round 9).

  • shell_host_zsh_missing_path_alias_mirrors_shared_map_widget — the
    two-phase mount captures bindings before any rebind, so an alias of
    main dispatches main's widget, never vicmd's (review round 9).

Verification

Focused (alinux3 arm64 container, zsh 5.5.1):

  • cargo test -p cosh-shell --test shell_host — new cases green; 53 zsh
    filter tests green. Pre-existing environment failures unrelated to this
    diff (heavy::raw_relay_host_shows_isolated_sudo_prompt_and_keeps_shell_usable,
    termios::cosh_owned_timeout_recovery_restores_pty_without_visible_command,
    tools::readonly_compound_tests::* reap-deadline pair) fail identically on
    the clean base commit (stash-control attribution).
  • cargo test -p cosh-shell --lib / --bin cosh-shell — green except the
    same pre-existing readonly_compound pair (also fails on clean base).
  • cargo clippy --workspace --all-targets -- -D warnings (container) green;
    cargo fmt --check green; check-layout.sh and check-test-inventory.sh
    green.

Real-machine acceptance (real PTY 120x40, real cosh-core adapter, real LLM):

  • Container zsh 5.5.1: FAIL→PASS on both defect scenarios (f1 full-width
    single-token absolute path, f2 relative path), slash-free control scenario
    unchanged; native error gone, agent takes over.
  • macOS zsh 5.9 spot check (f1): intercept + takeover + re-echo correct
    under a themed user prompt.

Not run: workspace-wide test targets beyond cosh-shell; macOS cosh-core
build (pre-existing Linux-only rustix::openat2 usage, unrelated).

Evidence

Fork-hosted, commit-SHA-pinned
(SunnyQjm/anolisa@c9a65b3e312458743766d4f530ed28b5d4ff0e8c, branch
pr-2210-assets). All screenshots are full-size (120x40) final frames
rendered from the real-PTY casts.

FAIL→PASS on the exact issue scenario (f1, container zsh 5.5.1):

Before (main b0a57a26) After (this PR)
head f1 fixed f1

Relative-path variant (f2):

Before After
head f2 fixed f2

Slash-free control (c1, no regression):

Before After
head c1 fixed c1

macOS zsh 5.9 spot check (f1, themed user prompt):

macos f1

Casts:
head-f1.cast ·
fixed-f1.cast

Closes #1943

@github-actions github-actions Bot added the component:cosh-ng src/cosh-ng label Aug 4, 2026
@SunnyQjm
SunnyQjm marked this pull request as ready for review August 4, 2026 10:01

@qoderai qoderai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

本次审查范围内未发现需要修改的问题。


🤖 Generated by QoderView workflow run

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

Copy link
Copy Markdown

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: 5d1af0b142

ℹ️ 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 src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh.rs Outdated
Comment thread src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh.rs Outdated
@SunnyQjm

SunnyQjm commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 5d1af0b
reviewed_at: 2026-08-04T10:12:58Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • Owner:production 变更仅落在 shell_host/marker/zsh.rs,zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(复用 owner 内 input_intent.sh);测试落在 tests/shell_host/marker.rs,与 bash 对侧既有锚点同位置。单 owner patch,无跨 owner 混杂,无 root src/*.rs 新增。
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pub。测试仅消费已登记的 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增内容全部为内嵌 zsh 脚本,复用 owner 内既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖。production Rust 代码零变更,无 cosh_shell:: self-crate path 新增。
  • 新 crate / 新依赖 / lockfile:均无。

大文件 / 阈值

  • marker/zsh.rs 净增 +71 行(两个 hunk:+14、+57),按 diff 推算改后约 690+ 行(估算,diff 无法确定精确值)。该文件未登记在 large-file-inventory;输入快照中 CI Test cosh-ng fast checksTest cosh-ng 均已 SUCCESS,按"行数判定以 CI gate 实测为准"口径未越 700 行阈值,不构成 finding。但余量接近零,后续增长将触发 owner note/拆分登记;bash 对侧(台账 934 行)已有 .sh asset extraction 计划,zsh 侧持续增长时建议沿用同路径治理。

Open Questions / 剩余风险

  • zsh.rs:178 附近 _cosh_should_intercept_missing_path 与 bash 五门 verdict 逐字镜像;分类器本体共用 input_intent.sh 保持单一事实源,但 gate 编排逻辑双侧并行,后续语义变化需双侧同步。per-shell 机制差异(DEBUG trap vs ZLE accept-line)决定 glue 无法直接共用,登记为已知结构风险而非阻断。
  • tests/shell_host/marker.rs 改后约 3200+ 行;测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。
  • 其他评审者已在 PR 上提出行为级意见(print -sr 绕过用户 history exclusion 策略、非 user:* 形式的 accept-line 定制在 fallback 链中丢失),超出本结构评审范围,仅提示作者跟进,不作为组织问题 finding。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照时 CI gate(Test cosh-ng fast checksTest cosh-ngBuild cosh-ng release)均已 SUCCESS;行数/阈值类判定以 CI gate 实测为准。
  • PR 自述 check-layout.shcheck-test-inventory.sh、clippy、fmt 及 focused shell_host 测试通过,无法独立核实。

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 5d1af0b to 27551a8 Compare August 4, 2026 11:23
@SunnyQjm

SunnyQjm commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 1 addressed (Codex P1 history policy, P2 builtin-alias accept-line) plus the layout gate split (zsh_marker.sh extraction, byte-identical script). History rewritten by amend:

SHA
Reviewed (round 1) 5d1af0b142ec666f73d058417a3e05609d5b2b43
Current head 27551a84

Full verification re-run on the new head: workspace clippy -D warnings, fmt, check-layout.sh, check-test-inventory.sh green; 37 zsh-filter shell_host tests green (incl. 2 new review-anchored tests); real-PTY f1 scenario re-verified end-to-end (agent takeover, no native error).

@SunnyQjm

SunnyQjm commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 27551a8
reviewed_at: 2026-08-04T14:27:13Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • Owner:production 变更落在 shell_host/marker/zsh.rs(622 → 10 行,diff 精确值)与新增 shell_host/marker/zsh_marker.sh。zsh 侧 slash-bearing 自然语言拦截(accept-line ZLE widget、五门 verdict)属 shell_host owner 的 shell 级输入意图分类职责,分类器本体复用 owner 内 input_intent.sh,保持单一事实源。slash/registry.rs:485:520 仅测试模块内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host) 可见性。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本,复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 提取方式与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)已登记的 .sh asset extraction 治理路径一致(参照 input_intent.sh 模式),zsh 侧先一步落地。若功能留在 zsh.rs 内将形成 622+≈90 ≈ 710+ 行的新越线 .rs,提取后规避了"新建越线文件必须拆分而非登记"的触发。
  • zsh_marker.sh 为 704 行(diff 精确值)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;但该 asset 出生即超 700 规模且后续增长无闸门,建议参照 bash 台账条目补一条 owner note 明确 .sh asset 治理口径(非阻断建议)。
  • 字节同一性:旧 raw string 以换行开头,新 .sh 首行为空行,include_str! 内容与旧内嵌字符串一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +440 行(改后约 3351 行,估算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • _cosh_should_intercept_missing_path 与 bash 五门 verdict 逐字镜像(zsh_marker.sh 内注释亦声明 "Body mirrors marker/bash.rs");分类器本体共用 input_intent.sh,但 gate 编排逻辑双侧并行,后续语义变化需双侧同步。per-shell 机制差异(DEBUG trap vs ZLE accept-line)决定 glue 无法直接共用,登记为已知结构风险而非阻断。
  • 当前 head 上存在一条 CHANGES_REQUESTED 评审,含两条行为级 P1(拦截成功后仍经用户 widget dispatch 可能执行非预期原生命令;直接重放 zshaddhistory hook 绕过 zsh 原生 fc -p 上下文恢复)。两者均指向新 .sh asset 内的行为逻辑,属行为层而非组织问题,超出本结构评审范围,但合并前必须由作者闭环。
  • 上一轮两条行为级意见(history policy、zle -A builtin alias)已在本 head 修复并附锚点测试,属行为层闭环。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照时本 head 的 CI(Test cosh-ng fast checksTest cosh-ngBuild cosh-ng release、PR Checks)均已 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs 行数为 diff 精确推算。
  • PR 自述 clippy、fmt、check-layout.shcheck-test-inventory.sh 及 focused shell_host 测试通过,无法独立核实。

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes for two blocking ZLE/history integration regressions reproduced against 27551a84 with the cosh-shell PTY harness. The PR's seven focused zsh tests pass, but they do not cover these user-customization paths.

Comment thread src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh Outdated
Comment thread src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh Outdated
@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 27551a8 to 72c5da8 Compare August 10, 2026 07:30
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 2 addressed (kongche-jbw P1 x2: intercept-path user-widget bypass, foreign zshaddhistory hook replay). History rewritten by amend + rebase onto latest main:

SHA
Reviewed (round 2) 27551a84
Current head 72c5da83

Re-verification on the new head: 39 zsh-filter shell_host tests green (incl. 2 new review-anchored tests: ...intercept_never_runs_user_widget_synthesis, ...foreign_history_hook_stays_uninvoked); workspace clippy -D warnings + fmt green (container); check-layout.sh + check-test-inventory.sh green. The --lib/--bin readonly_compound reap-deadline pair remains the previously attributed pre-existing environment failure (fails identically on clean base).

@kongche-jbw both reproductions should now be closed off — happy to re-run your PTY scenarios if anything still trips.

@kongche-jbw
kongche-jbw self-requested a review August 10, 2026 09:44

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 6ece763ed999...72c5da8346bc

[P1] Keep URL-shaped tokens on the native path

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:185

The missing-path context disables the slash veto without adding the URL counterproof required by
#1943. In a readable working directory, https://example.invalid/path 请帮我打开 proves missing
and classifies as natural_language, so this branch sends a URL-shaped command to the agent
instead of preserving zsh's native result. Please reject URL schemes before this verdict and add
a PTY counterproof with a URL first token plus natural-language text.

[P1] Wrap the widget bound to the submit key

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:710

Replacing the widget named accept-line does not affect a keymap that directly binds ^J or
^M to accept-line-and-down-history or a user widget. After this mount, bindkey '^J' still
points to that alternate widget, so _cosh_accept_line is unreachable and the slash-bearing NL
input reaches the original raw path error. Preserve and wrap the active submit-key bindings across
keymaps, then add a PTY test using a direct bindkey customization.

[P2] Apply the complete in-memory history policy

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:687

_cosh_history_policy_allows only honors HIST_IGNORE_SPACE and hooks. print -s also bypasses
options such as HIST_IGNORE_DUPS: with that option set, two identical intercepted prompts are
both inserted, whereas native submission keeps only one. This makes the new route persist history
entries the user asked zsh to suppress. Cover the applicable duplicate/reduction options or avoid
the raw history insertion, with a repeated-prompt regression test.

[P2] Do not evaluate PS1 again while re-echoing

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:694

${(%%)PS1} performs prompt expansion. With PROMPT_SUBST and a PS1 containing $(...), an
intercepted line runs that command substitution here and then again when zsh renders the next
prompt. Side-effecting or expensive prompt commands therefore run one extra time only on this
route. Reuse the prompt already rendered by ZLE, or re-echo without evaluating PS1, and add a
PROMPT_SUBST counter test.

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 72c5da8 to 5e4d258 Compare August 10, 2026 13:59
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 3 addressed in 5e4d2581 (amend + rebase onto latest main). All four findings verified locally and accepted. Two of them landed in domains that had now produced counter-examples across ≥2 consecutive rounds (mounting: builtin-alias → empty-buffer synthesis → direct bindkey; history: hooks → fc -p context → IGNORE_DUPS), so per our review circuit-breaker both domains were redesigned instead of point-fixed:

[P1] URL-shaped tokens — valid (verified: https: proves ENOENT in a readable cwd and the Han-bearing line classifies as natural_language; the design matrix's "URL veto" row was an unverified assumption never turned into a test). Fix: the gate vetoes [a-zA-Z]*://* first words before any filesystem probe. Test: shell_host_zsh_missing_path_url_first_word_stays_native (URL + NL text keeps the native error). The bash-side sibling gate has the same shape (#1942 legacy); tracked separately rather than widening this zsh-only PR.

[P1] Submit-key bindings — valid. Redesign: submission is now modeled on keymap bindings, not the widget name. The mount claims ^M/^J across main/emacs/viins/vicmd when they bind a non-accept-line widget, remembers the original per key, and the pass-through dispatch routes $KEYS back to it; successful intercepts still finalize on the builtin only. Contract boundary recorded in the spec: accept variants bound to other keys are an explicit non-goal (fail-open). Test: shell_host_zsh_missing_path_intercepts_with_direct_submit_key_binding (direct bindkey '^M'/'^J' to a user widget: interception reachable, pass-through keeps the user widget, intercepted line never enters it).

[P2] In-memory history policy — valid (reproduced: two identical intercepted prompts both inserted under HIST_IGNORE_DUPS). Redesign: the manual history re-add is removed entirely — options × hooks × fc -p contexts form an open-ended bypass surface, and three rounds each found a new one. Intercepted lines are now never re-added; the failure direction is a non-recallable prompt, never a persisted line the user asked zsh to suppress. Test: shell_host_zsh_missing_path_intercepted_lines_stay_out_of_history (repeated prompt, zero history entries).

[P2] PS1 re-evaluation — valid (reproduced: ${(%%)PS1} runs PROMPT_SUBST command substitutions; a probe also showed zle -I repaint adds another). Fix: the re-echo defers to the next precmd as plain text — the intercept route no longer touches prompt rendering at all. Test: shell_host_zsh_missing_path_intercept_does_not_reevaluate_ps1 (side-effecting $(...) in PS1 evaluates exactly as often as native submission).

SHA
Reviewed (round 3) 72c5da83
Current head 5e4d2581

Verification on the new head: 42 zsh-filter shell_host tests green (incl. the four new review-anchored tests); workspace clippy -D warnings + fmt green; check-layout.sh + check-test-inventory.sh green; real-PTY f1/f2/c1 re-run — no native error, agent takeover, control unchanged. Full shell_host had 4 environment failures under parallel load (heavy::...sudo_prompt..., three termios::...) that all pass solo on the same head; the --lib/--bin readonly_compound pair remains the previously attributed pre-existing failure.

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 11, 2026 02:21

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 43d939139df6...5e4d2581784b

[P1] Preserve submit widgets per keymap

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:733

_COSH_SUBMIT_KEY_WIDGETS is indexed only by $KEYS, although the mount scans four keymaps.
If emacs and viins bind ^M to different widgets, the later scan overwrites the saved
emacs widget. Pressing Enter in emacs then invokes the viins widget for every pass-through
line; a mode-specific widget may rewrite or submit a different buffer.

Possible direction: key saved widgets by keymap plus key (or use per-keymap wrappers), and add
a PTY test with distinct ^M widgets in emacs and viins that exercises both active maps.

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 5e4d258 to 0c5864e Compare August 11, 2026 02:58
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 4 addressed in 0c5864e1 (amend + rebase onto latest main).

[P1] Preserve submit widgets per keymap — valid — accepted and fixed.

Verification: confirmed the flat $KEYS table lets a later scan overwrite the earlier entry; with distinct ^M widgets the insert map would delegate to the vicmd widget. A container probe also pinned the runtime facts: at dispatch time ZLE reports the active insert-mode map as main (emacs/viins resolve through it) and vicmd by its own name.

Fix: the delegation table is keyed by ${KEYMAP}:${KEYS} and the claim scans exactly main + vicmd (scanning emacs/viins by name would create entries the dispatcher can never match, and rebind inactive maps). A mode-specific widget is now never invoked from the other mode. Spec contract updated accordingly (runtime keymap-alias re-arrangement, e.g. bindkey -A mid-session, stays a fail-open boundary).

Evidence: new test shell_host_zsh_missing_path_submit_key_widgets_stay_per_keymap — user rcfile binds ^M/^J to a main-map widget and different vicmd widgets on the same keys; asserts interception still fires, pass-through lines reach the main widget, and the vicmd widget log stays empty. Anchoring counter-proved: reverting to the flat table makes this test fail exactly as your reproduction describes, the per-keymap table turns it green. Also re-verified: 43 zsh-filter shell_host tests green, workspace clippy -D warnings + fmt + check-layout.sh + check-test-inventory.sh green, real-PTY f1 re-run (no native error, agent takeover).

SHA
Reviewed (round 4) 5e4d2581
Current head 0c5864e1

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 11, 2026 02:59

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 4349e319a682...0c5864e1000e

[P1] Prevent recursive dispatch through accept-line

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:653

A directly bound submit widget may legitimately finish with zle accept-line. On a pass-through
line, this function invokes that widget; its accept-line call re-enters _cosh_accept_line, which
dispatches the same widget again. Binding both ^M and ^J to such a widget and submitting
echo ok repeats the widget until zsh reports maximum nested function level reached, so native
lines can multiply widget side effects and fail submission.

Possible direction: guard delegated dispatch so a re-entrant accept-line call reaches
_cosh_orig_accept_line, and add a PTY test whose directly bound widget uses zle accept-line
and runs exactly once.

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 0c5864e to 778126f Compare August 11, 2026 03:39
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 5 addressed in 778126fd (amend + rebase onto latest main).

[P1] Prevent recursive dispatch through accept-line — valid — accepted and fixed.

Verification: confirmed — a delegated widget finishing with the NAMED zle accept-line re-enters the wrapper while $KEYS still matches the claimed key, so the flat dispatch re-invoked the same widget; a container probe reproduced the recursion (nested-function limit without a guard).

Fix: delegated dispatch (both the submit-key table hit and the saved-alias branch) now sets an in-progress flag around the zle call — cleared by an always block on every exit path — and a re-entrant accept-line call that observes the flag goes straight to .accept-line. The user's widget runs exactly once per submission and the line still submits; intercept finalization is unchanged (it never enters the dispatch chain).

Evidence: new test shell_host_zsh_missing_path_reentrant_accept_line_runs_widget_once — rcfile binds ^M/^J to a widget that logs and finishes with zle accept-line; asserts the pass-through line executes, no maximum nested error, and the widget log contains exactly one entry per submission; interception on the same mount still fires. Re-verified: 44 zsh-filter shell_host tests green, workspace clippy -D warnings + fmt + check-layout.sh + check-test-inventory.sh green.

SHA
Reviewed (round 5) 0c5864e1
Current head 778126fd

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 11, 2026 03:50

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 4349e319a682...778126fdbef7

[P1] Preserve the saved accept-line chain on re-entry

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:679

When ^M is bound to widget A and accept-line is customized to widget B, A may finish with
zle accept-line to invoke B. The new in-progress branch instead jumps directly to
.accept-line, so B never runs on pass-through submissions; its buffer transforms, validation,
or side effects are silently lost. The added test leaves accept-line at the builtin and misses
this combined customization.

Possible direction: Track key-widget delegation separately from saved-accept-line delegation.
First re-entry should invoke _cosh_orig_accept_line; only a further recursive entry should reach
the builtin. Please add a PTY regression with both custom widgets active.

@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

@kongche-jbw Rounds 4–5 were consecutive point-fixes in the same delegation-chain domain, which is a signal the input space was never enumerated up front — so instead of waiting for the next counter-example, here is the complete dispatch state machine we now anchor the implementation and tests to. It is also recorded in the design spec as the ruling contract for this domain.

Invariants

  • I-a: a line that passes the five gates never executes user code.
  • I-b: a line that does not pass behaves exactly per user configuration (the bound widget runs exactly once).
  • I-c: any internal failure falls open to native submission; no line is ever swallowed.

Entry × context × gate × delegation matrix

# Entry Context/condition Outcome Invariant Anchor
S1 Key press, key not claimed (accept-line name binding) start, gates fail table miss → saved alias → builtin I-b all pass-through tests
S2 same start, gates pass intercept → builtin I-a positive tests (f1/f2)
S3 Key press, key claimed start, gates fail table hit → user widget (flag set) I-b ...direct_submit_key_binding
S4 same start, gates pass intercept → builtin, user widget untouched I-a same + ...never_runs_user_widget_synthesis
S5 Named zle accept-line from the delegated widget (re-entrant, flag=1) any builtin (widget exactly once, line submits) I-b ...reentrant_accept_line_runs_widget_once
S6 Named zle accept-line from any other widget (flag=0, $KEYS = outer trigger key) start gates evaluate → table lookup by ${KEYMAP}:${KEYS}: unclaimed outer key → alias/builtin; claimed key → that key's declared widget I-a/I-b isomorphic to S1–S3
S7 Key press (claimed or not) cont / select / vared always delegated, gates skipped I-b heredoc test (E1×cont); E2×cont isomorphic
S8 Claimed widget meanwhile deleted any table-hit guard fails → alias/builtin I-c existence check in dispatch
S9 bindkey -s / zle -U synthesized sequences as key presses identical to S1–S4 (processed per key) isomorphic
S10 Delegated widget throws any always clears the flag; error propagates natively I-c guard structure
S11 Delegated widget opens zle recursive-edit, submit key pressed inside flag still 1 → builtin (that submission skips gates and the user widget once) declared non-goal: fail direction is one non-intercepted submission, never a doubled widget declared design spec §3.1
S12 Post-mount rebinds / keymap alias re-arrangement after mount fail-open (interception may disarm; native behavior intact) I-c contract boundary

Ruling contract: a new finding in this domain should locate its cell — existing cells are ruled by their outcome and invariant; only an entry mode outside the matrix constitutes a design gap (which would trigger a redesign, not another patch). S11 is the one combination we explicitly declare out of scope — please flag if you disagree with that boundary.

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 4349e319a682...778126fdbef7

[P1] Preserve the saved accept-line target in S5

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:679

The proposed S5 outcome still changes a valid combined setup. If Enter is bound to widget A and
accept-line names widget B, A may call zle accept-line specifically to delegate to B. The guard
at this line jumps to .accept-line, so B's buffer edits, validation, and side effects disappear.
This violates I-b even though A runs once: native behavior is A once, followed by B once.

Possible direction: distinguish key-widget dispatch from saved-accept-line dispatch. The first
re-entry from A should call _cosh_orig_accept_line; only a further recursive entry should reach
the builtin. Please add a PTY regression with distinct A and B widgets.

@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 778126f
reviewed_at: 2026-08-12T04:45:11Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 提取脚本体为 zsh_marker.sh,新增 accept-line ZLE 拦截逻辑在 .sh asset 内)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责,分类器本体复用 owner 内 input_intent.sh,单一事实源不变。slash/registry.rs:488:523mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host)。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本,复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致(参照 input_intent.sh 先例),zsh 侧先一步落地;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 769 行(diff 精确值,round 3–5 的 keymap claim 与重入 guard 较 round 2 的 704 行继续增长)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;但该 asset 出生即超 700 且持续无闸门增长,重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:旧 raw string 以换行开头,新 .sh 首行为空行,include_str! 内容与旧内嵌字符串一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +949 行(改后约 3860 行,按 diff 增量估算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • zsh_marker.sh_cosh_should_intercept_missing_path 与 bash 五门 verdict 逐字镜像(文件内注释亦声明 "Body mirrors marker/bash.rs");分类器本体共用 input_intent.sh,但 gate 编排双侧并行,后续语义变化需双侧同步。per-shell 机制差异(DEBUG trap vs ZLE accept-line)决定 glue 无法直接共用,登记为已知结构风险而非阻断。
  • 当前 head 上仍有两条未闭环的行为级 P1(zsh_marker.sh:679:重入 guard 直跳 .accept-line,跳过 key widget A 经 zle accept-line 委托的自定义 accept-line widget B,违反作者自定的 I-b 不变式),属行为层而非组织问题,超出本结构评审范围,但合并前必须由作者闭环。
  • 作者已声明 dispatch 状态机矩阵(S1–S12)为该领域 ruling contract,后续评审可按该矩阵定位新 finding 是否落在已有 cell。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照时本 head 的 CI(Test cosh-ng fast checksTest cosh-ngBuild cosh-ng release、PR Checks)均已 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs/.sh 行数为 diff 精确值,测试文件行数为估算。
  • PR 自述 clippy、fmt、check-layout.shcheck-test-inventory.sh 及 44 个 zsh-filter shell_host 测试通过,无法独立核实。

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 778126f to 1cfdacc Compare August 12, 2026 11:22
@SunnyQjm

SunnyQjm commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 1cfdacc
reviewed_at: 2026-08-18T10:41:24Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 622 → 10 行、新增 zsh_marker.sh 769 行,均为 diff 精确值)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(standard.md 明确自然语言意图分类归 shell_host/input_intent 而非 input/),分类器本体复用 owner 内 input_intent.sh,单一事实源不变;shell_host 下新增 .sh asset 有 input_intent.sh 先例。slash/registry.rs:486:521mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host)。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本,复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致,zsh 侧先一步落地;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 769 行(diff 精确值)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:旧 raw string 以换行开头,新 .sh 首行为空行,include_str! 内容与旧内嵌字符串一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +949 行(hunk 位于文件末尾,改后约 3859 行,diff 推算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • 本 head(1cfdaccd)与本 automation 2026-08-12 已评审 head 相同,diff 无变化;新增动态为 kongche-jbw 2026-08-18 评审:一条新 blocking 行为级 P1(zsh_marker.sh missing-path gate 只把首词当文件系统路径处理,zsh 可将 slash-bearing token 解析为 function/alias 或 glob 展开——路径/run 函数、目录*/run glob 会被误判拦截)+ 此前 zsh_marker.sh:679 重入 guard 跳过 saved accept-line target 的 P1 仍未闭环。均属行为层而非组织问题,超出本结构评审范围,但合并前必须由作者闭环。
  • _cosh_should_intercept_missing_path 与 bash 五门 verdict 逐字镜像(含 URL veto 门);分类器本体共用 input_intent.sh,但 gate 编排双侧并行,后续语义变化需双侧同步。per-shell 机制差异(DEBUG trap vs ZLE accept-line)决定 glue 无法直接共用,登记为已知结构风险而非阻断。注意:上述新 P1 的修复若落在 zsh 侧 gate,需评估 bash 对侧是否存在同源问题。
  • 作者已声明 dispatch 状态机矩阵(S1–S12)为该领域 ruling contract,后续评审可按该矩阵定位新 finding 是否落在已有 cell。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照中本 head 的 CI(Test cosh-ngTest cosh-ng fast checksBuild cosh-ng releasePR ChecksCommit Message Lint,2026-08-12 完结)均 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs/.sh 行数为 diff 精确值,测试文件行数为 diff 推算。
  • PR 自述 clippy、fmt、check-layout.shcheck-test-inventory.sh 及 44 个 zsh-filter shell_host 测试通过,无法独立核实。

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 4b131c4faa2a...1cfdaccd2675

Found one new blocking correctness issue in the current head. The previously reported P1 at zsh_marker.sh:679 (re-entry skips the saved accept-line target in the combined A → B widget chain) is unchanged and remains blocking; I have not duplicated that inline comment.

Validation: reproduced the new issue with zsh 5.9 on aarch64 Linux using the exact current gate/classifier logic. Relevant GitHub CI checks are green, but they do not cover this Zsh resolution path.

Comment thread src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh
@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 1cfdacc to 5b35d40 Compare August 18, 2026 12:47
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 6 addressed in 5b35d405 (amend + rebase onto latest main). Both items verified and accepted; the gate fix is a design-level convergence per the gate-domain completeness contract, not another point veto.

[P1] Keep Zsh-resolved commands on the native path — valid — fixed by mirroring zsh command-word resolution.

Verification: reproduced in a container (zsh 5.5.1 and PTY tests): function 路径/run executes natively while the previous gate classified the literal token as provably missing and consumed the line; a Han-glob first word (目录*/run) was likewise interceptable because the Han-branch veto list omits glob metacharacters.

Fix (design-level): the gate's legitimacy premise is "zsh would necessarily report no such file or directory for this line". This wrapper fires before zsh parses the line (bash's hook fires after resolution already failed), so the gate now reproduces zsh's own resolution order for a slash-bearing word:

  • Gate 1.6: builtin whence -w consults zsh's resolver — anything it resolves (alias, function, any resolvable form) stays native;
  • Gate 1.7: a first word carrying unquoted glob metacharacters (* ? [ ] ( ) < > ^ # ~) conservatively stays native — the literal token proving missing on disk proves nothing about its expansion.

The design spec now records the full resolution-order table (alias → function → glob expansion → path exec, each level with its gate outcome) as the ruling contract for this domain; suffix aliases are the one declared non-goal boundary. The shared classifier is intentionally unchanged: on the bash side the hook only fires after expansion/resolution already failed, so the Han-branch metacharacter behavior is not reachable as a false intercept there — the compensation belongs to the pre-parse zsh gate.

[P1] Re-entry skips the saved accept-line target (A → B chain) — valid — round 5's implementation deviated from your suggested direction (it terminated on the builtin instead of _cosh_orig_accept_line), which dropped a user accept-line wrapper from the chain. First re-entry now reaches the saved target (B stays in the A → B chain); only a second-level re-entry (the saved target itself invoking the NAMED accept-line) terminates on the builtin to keep the chain finite. Dispatch matrix cells S5a/S5b updated accordingly.

Evidence: three new PTY tests —

  • shell_host_zsh_missing_path_slash_function_stays_nativefunction 探针1943/run runs natively with its arguments, no intercept event;
  • shell_host_zsh_missing_path_han_glob_stays_native目录*/run 帮我处理一下 expands to an existing executable and runs, no intercept event;
  • shell_host_zsh_missing_path_reentrant_reaches_saved_accept_line — bound widget A finishing with the named accept-line reaches saved wrapper B; A and B each run exactly once, no recursion.

Anti-regression: with the fix reverted (tests kept) all three fail; restored, the zsh-filter suite is 47/47 green. Workspace clippy -D warnings, fmt, check-layout.sh, check-test-inventory.sh all green after rebasing onto latest main.

SHA
Reviewed (round 6) 1cfdaccd
Current head 5b35d405

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 18, 2026 12:48

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 52eeb520147b...5b35d4058965

[P1] Parse command words before proving them missing

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:196

first_word is still raw BUFFER text here. Zsh runs a defined 路径/run
function for "路径/run" 帮我运行, but whence receives the quotes and the path
probe treats "路径 as missing, so this gate returns natural_language and
consumes a valid command. Brace expansion has the same problem:
/b{in,路径}/echo 帮我运行 executes /bin/echo natively but is intercepted here.
Possible direction: safely normalize a literal command word, or fail open on
quoting, escaping, and every pre-execution expansion form. Add PTY counterproofs
for quoted slash functions or paths and brace-expanded commands.

[P1] Preserve bindings when keymaps alias the same map

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:788

If an rcfile binds a custom Enter widget and then runs bindkey -A main vicmd
before this marker loads, main and vicmd share one map. The scan saves the
original only under main; its rebind is already visible through vicmd, so the
second scan skips it. In vicmd, KEYMAP=vicmd, the lookup at line 673 misses, and
the global accept-line target silently replaces the user's submit transform or
validation on every native line. Possible direction: capture every logical map
before rebinding aliased maps, or mirror the saved entry. Add a PTY regression
that aliases vicmd to main, enters vicmd, and verifies the widget runs once.

@SunnyQjm

SunnyQjm commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 5b35d40
reviewed_at: 2026-08-19T02:37:55Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 622 → 10 行、新增 zsh_marker.sh 800 行,均为 diff 精确值)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(standard.md 明确自然语言意图分类归 shell_host/input_intent 而非 input/),分类器本体复用 owner 内 input_intent.sh,单一事实源不变;shell_host 下新增 .sh asset 有 input_intent.sh 先例。slash/registry.rs:486:521mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host)。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本(round 6 的 whence -w 解析门、glob 元字符 veto 门、重入链修复均在 .sh asset 内),复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致,zsh 侧先一步落地;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 800 行(diff 精确值;704 → 769 → 800 逐轮增长)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:旧 raw string 以换行开头,新 .sh 首行为空行,include_str! 内容与旧内嵌字符串一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +1129 行(hunk 位于文件末尾,改后约 4040 行,diff 推算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • 本 head(5b35d405)与本 automation 2026-08-18 已评审 head 相同,diff 无变化。新增动态为 BryanHeBY 2026-08-19 评论:zsh_marker.sh:655-658 注释中 "its DEBUG trap cannot veto execution" 前提被指事实错误(zshbuiltins(1) 记载 DEBUG_BEFORE_CMD + ERR_EXIT 可跳过命令),请求改写注释依据;该评论同时指出 ZLE 路线的真实理由(sublist 粒度、ZSH_DEBUG_CMD 文本重构、续行隔离、用户选项污染)从未与 DEBUG 路线做过比较。属注释/设计论证层而非组织问题,超出本结构评审范围,但建议作者闭环——若采纳为注释改写,仅触及 .sh asset 文本,无结构影响。
  • 此前 kongche-jbw 在同 head 留有两条 blocking 行为级 P1(zsh_marker.sh:196 引号/brace expansion 误判、:788 keymap alias 共享 map 查表落空),合并前须由作者闭环;两者若修复均落在 .sh asset 内,预计无组织结构变化。
  • _cosh_should_intercept_missing_path 与 bash 五门 verdict 并行维护的既定风险仍在;round 6 的 whence -w + glob veto 为 zsh-only 显式发散,后续 gate 语义变化需重新评估 bash 对侧。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照中本 head 的 CI(Test cosh-ngTest cosh-ng fast checksBuild cosh-ng releasePR ChecksCommit Message Lint,2026-08-18 完结)均 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs/.sh 行数为 diff 精确值,测试文件行数为 diff 推算。
  • PR 自述 47 个 zsh-filter shell_host 测试、clippy -D warnings、fmt、check-layout.shcheck-test-inventory.sh 通过,无法独立核实。

@BryanHeBY

Copy link
Copy Markdown
Collaborator

审阅 @ 5b35d405:P1 —— 设计依据的注释是事实错误

先说清范围:这一条只针对注释里的技术前提,不要求改路线。 ZLE 很可能仍是对的选择,但支持它的理由与注释里写的不是同一条,而假前提会误导下一个维护者。

注释断言

zsh_marker.sh:655-658:

# ── Slash-bearing natural-language interception (#1943) ──
# zsh executes a slash-bearing command word as a path without invoking
# command_not_found_handler, and its DEBUG trap cannot veto execution, so
# the only pre-execution seam is the accept-line widget.

zshbuiltins(1) 写的正相反

If sig is DEBUG then arg will be executed before each command if the option
DEBUG_BEFORE_CMD is set (as it is by default) … If DEBUG_BEFORE_CMD is set various
additional features are available. First, it is possible to skip the next command by
setting the option ERR_EXIT
… Also, the shell parameter ZSH_DEBUG_CMD is set to the
string corresponding to the command to be executed following the trap.

zshmisc(1) 的 TRAPDEBUG 条目确认 DEBUG_BEFORE_CMD 默认开。

交互 zsh 实测(zsh 5.9,trap 经 ZDOTDIR rc 加载,pty)

trap 'if [[ $ZSH_DEBUG_CMD == *SKIPME* ]]; then setopt ERR_EXIT; else unsetopt ERR_EXIT; fi' DEBUG
$ echo ALIVE_1                            → ALIVE_1
$ echo please-SKIPME-this                 → (跳过,无输出)
$ echo ALIVE_2                            → ALIVE_2
$ print -r -- "STILL_HERE:$ZSH_SUBSHELL"  → STILL_HERE:0    ← 顶层 shell 存活,非子 shell
$ exit 7

选择性 veto 可用,且 shell 不死。ZSH_SUBSHELL=0 排除了「其实跑在子 shell 里」这种解释。

三方机制对照:

$ bash -c 'shopt -s extdebug; trap "false" DEBUG; echo X'   → (空,vetoed)
$ zsh  -c 'trap "false" DEBUG; echo X'                      → X      ← #1943 probe 观察到的
$ zsh  -c 'trap "setopt ERR_EXIT" DEBUG; echo X'            → (空,vetoed)

#1943 的 probe 只证明了 bash 的机制(非零返回)在 zsh 上不成立;「no veto-execution semantics」是从一次否定 probe 做的全称否定。该结论从 issue 的 probe 表流进了这里的源码注释。

顺带一提,同一 codebase 的 bash 侧对这套机制理解很深 —— #2632bash_marker_frames.sh:155 专门推理了 extdebug veto 会怎样 brick 会话。zsh 侧却断言 zsh 没有对应物。

支持 ZLE 的真实理由(注释一条都没给)

我倾向认为路线本身没选错,但依据应该是这四条:

  1. 粒度。DEBUG 按 sublist 触发(zshbuiltins:「a command' is what is described as a sublist'」),git status; /explain this 会触发两次;NL 判定要的是整条提交行,只有 ZLE 的 $BUFFER 是。
  2. 文本保真。zshbuiltins 明确警告 ZSH_DEBUG_CMD「is reconstructed from the internal format and may not be formatted the same way as the original text」。自然语言形态启发式吃的正是原始文本。
  3. 续行隔离。ZLE $CONTEXT(start/cont)天然区分 heredoc/PS2 续行 —— [cosh-ng] zsh: slash-bearing natural-language prompts bypass interception (bash fixed in #1942) #1943 自己列到了这一条。
  4. 用户状态污染ERR_EXIT 是用户可见选项,在 trap 里 toggle 会覆盖用户自己的 setopt ERR_EXIT,需要保存-恢复。

请求

:656 那句改写为上面这些成立的理由,删掉「its DEBUG trap cannot veto execution」。

是否要重新评估路线由 owner 定 —— 我不认为一条前提纠正值得推翻 1900 行。但代价要说清:因为前提是假的,ZLE 的风险面(_cosh_dispatch_accept_line 那段关于重入、keymap 抢绑、$KEYS 路由的推理,正是 #1943 预判的「compatibility matrix with user ZLE customizations / other widget-wrapping plugins」的具体形态)从未与 DEBUG-trap 路线的粒度/保真限制做过比较。方向对不对,应该由比较得出。

zshbuiltins(1) / zshmisc(1) 引文为原文直引;探针在本机 zsh 5.9 实测。probe 表本身的纠正我提到 #1943 去,这里只处理源码注释。

@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 7 addressed in 86e37c72 (amend + rebase onto latest main). Both items verified and accepted; the gate fix generalizes the resolution-mirror contract to the lexing/expansion layer, closing the whole class rather than the two reported shapes.

[P1] Parse command words before proving them missing — valid — the raw first word has seen no lexing or expansion, so quoting ("路径/run") and brace expansion (/b{in,路径}/echo) both defeat literal probing. Reproduced in a container: the quoted function runs natively while the previous gate consumed the line.

Fix (design-level): the round-6 glob veto generalizes into a literal-word whitelist — instead of enumerating dangerous characters (a blacklist always misses the next form), the gate only continues for a first word made purely of non-syntax characters (alphanumerics, _./-, multibyte text). Any other ASCII character — quotes, backslashes, braces, $, backticks, glob metacharacters, and every future expansion form — fails open to the native path. The core interception scenario (bare Han prompt + path) is untouched; a quoted NL line no longer intercepting is the acceptable fail-open direction. The design spec's resolution-order table now includes the lexing and pre-execution-expansion levels with this whitelist as their ruling outcome.

[P1] Preserve bindings when keymaps alias the same map — valid — with bindkey -A main vicmd both keymaps share one physical map, so the second claim scan saw the first scan's rebind and skipped the key, leaving the vicmd dispatch lookup to miss and silently replace the user's widget. The scan now mirrors the saved entry from the earlier keymap instead of skipping, so a vicmd-mode submission still reaches the user's widget.

Evidence: three new PTY tests —

  • shell_host_zsh_missing_path_quoted_function_stays_native"探针1943q/run" 帮我运行一下 runs the defined function natively, no intercept event;
  • shell_host_zsh_missing_path_brace_expansion_stays_native/b{in,路径}/echo … expands to /bin/echo and runs, no intercept event;
  • shell_host_zsh_missing_path_aliased_keymap_keeps_user_widget — after bindkey -A main vicmd, a vicmd-mode submission reaches the user's widget exactly once (submit:vicmd:… logged once).

Anti-regression: with the fix reverted (tests kept) all three fail; restored, the zsh-filter suite is 50/50 green. Workspace clippy -D warnings, fmt, check-layout.sh, check-test-inventory.sh all green after rebasing onto latest main.

SHA
Reviewed (round 7) 5b35d405
Current head 86e37c72

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 19, 2026 02:48

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 0a190d509b8e...86e37c72df18

[P1] Claim submit keys in user-defined keymaps

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:811

The mount scans only main and vicmd. If an rcfile creates a custom keymap,
binds ^M/^J there to a widget that finishes with zle .accept-line, and
selects that map from zle-line-init, Enter never reaches _cosh_accept_line.
The issue input then still produces the native no such file or directory
error and no intercept marker. This setup exists before the marker loads, so it
is not the declared post-mount rebind boundary.

Possible direction: enumerate user-defined keymaps at mount time, preserve
aliases as this table already does, and add a PTY regression that submits from
a custom map.

@SunnyQjm

SunnyQjm commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 86e37c7
reviewed_at: 2026-08-20T03:44:14Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • 变更范围:本 head(86e37c72)与本 automation 2026-08-19 已评审 head 相同,diff 无变化;本次触发的新增动态仅为 kongche-jbw 2026-08-20 对既有 custom keymap P1 的复验评论(见 Open Questions)。
  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 622 → 10 行、新增 zsh_marker.sh 820 行,均为 diff 精确值)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(standard.md 明确自然语言意图分类归 shell_host/input_intent 而非 input/),分类器本体复用 owner 内 input_intent.sh,单一事实源不变;shell_host 下新增 .sh asset 有 input_intent.sh 先例。slash/registry.rs:486:521mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host)。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本,复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 820 行(diff 精确值)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:新 .sh 首行为空行,与旧 raw string 以换行开头一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +1292 行(改后约 4203 行,diff 推算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • 当前 head 上仍有未闭环的行为级 blocking 条目:kongche-jbw 2026-08-20 复验确认 zsh_marker.sh:811 P1 在本 head 依然成立(mount 只扫描 main/vicmd,rcfile 自建 keymap 经 zle-line-init 选中时 Enter 永远到不了 _cosh_accept_line,拦截整体失效,且该场景在 mount 前已存在、不被 post-mount rebind 边界覆盖);BryanHeBY 指出的 zsh_marker.sh:655-658 注释事实错误(DEBUG_BEFORE_CMD + ERR_EXIT 可 veto,注释仍断言 "its DEBUG trap cannot veto execution")在本 head 的 diff 文本中仍未改写。均属行为/注释层而非组织问题,超出本结构评审范围,但合并前须由作者闭环;两者修复预计仍落在 .sh asset 内,无组织结构变化。
  • _cosh_should_intercept_missing_path 与 bash verdict 并行维护的既定风险仍在;round 6/7 的 whence -w、literal-word whitelist 为 zsh-only 显式发散(zsh wrapper 在 parse 前触发,bash hook 在 resolution 失败后触发),后续 gate 语义变化需重新评估 bash 对侧是否存在同源问题。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照中本 head 的 CI(Test cosh-ngTest cosh-ng fast checksBuild cosh-ng releasePR ChecksCommit Message Lint,2026-08-19 完结)均 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs/.sh 行数为 diff 精确值,测试文件行数为 diff 推算。
  • PR 自述 50 个 zsh-filter shell_host 测试、clippy -D warnings、fmt、check-layout.shcheck-test-inventory.sh 通过,无法独立核实。

Copy link
Copy Markdown
Collaborator

Re-verified against 86e37c72: this P1 is valid.

The mount loop only claims main and vicmd. With a user-defined keymap selected by zle-line-init { zle -K review-map }, the current mount leaves ^M bound to the user's widget and creates no _COSH_SUBMIT_KEY_WIDGETS[review-map:$'\r'] entry. If that widget finishes with zle .accept-line, _cosh_accept_line is never reached, so the slash-bearing NL input still takes the native no such file or directory path.

Minimal zsh 5.9 evidence after applying the current claim loop:

custom-enter="^M" _user_submit
saved-custom=missing

Because the custom map and binding exist in the rcfile before the marker loads, this is outside neither the mount contract nor the declared post-mount-rebind boundary. The finding should remain blocking until the mount handles pre-existing user-defined keymaps and a PTY regression covers that path.

Related review: #2210 (review)

@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 8 addressed in 97f275cb (amend + rebase onto latest main). Apologies for the delayed handling — the review (submitted minutes after the R7 push) was missed by my activity-window filter and only surfaced with your re-verification comment.

[P1] Claim submit keys in user-defined keymaps — valid — the main/vicmd claim enumeration rested on the round-4 probe showing runtime $KEYMAP reports only main/vicmd, which holds for the standard emacs/vi modes but not for a map selected via zle -K from zle-line-init. Reproduced in a container: with review-map bound and selected, Enter never reached the wrapper and the issue input took the native error path.

Fix (design-level): zsh's builtin keymap set is closed (emacs viins vicmd viopp visual plus the modal maps isearch command .safe menuselect listscroll, and the main alias), so the claim set generalizes to main + vicmd + every non-builtin keymap reported by bindkey -l — any user-created map existing at mount time is claimed, with the round-7 alias mirror now consulting every saved table entry. The modal builtins stay unclaimed deliberately: their submit keys do not mean "accept a command line" (accept-search, menu accept, execute-named-cmd), so claiming them would break mode semantics.

Evidence: new PTY test shell_host_zsh_missing_path_custom_keymap_still_intercepts — the rcfile creates review-map, binds ^M/^J there to a widget finishing with zle .accept-line, and selects it from zle-line-init; the NL input is intercepted and a pass-through line reaches the user's widget exactly once (submit:review-map:… logged once). Anti-regression: with the fix reverted (test kept) it fails; restored, the zsh-filter suite is 51/51 green. Workspace clippy -D warnings, fmt, check-layout.sh, check-test-inventory.sh all green after rebasing onto latest main.

SHA
Reviewed (round 8) 86e37c72
Current head 97f275cb

@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 86e37c7 to 97f275c Compare August 20, 2026 03:53
@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 20, 2026 03:53

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 8f2416470233...97f275cb701d

[P1] Preserve whitespace in custom keymap names

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:818

bindkey -N 'review map' main is valid, and bindkey -l emits that name on one line.
The unquoted command substitution here splits it into review and map, so the actual
map is never claimed. If zle-line-init selects that map and its submit widget calls
zle .accept-line, Enter bypasses _cosh_accept_line; the slash-bearing prompt still
ends in zsh's native no such file or directory instead of an intercept.

Possible direction: iterate bindkey -l line by line and cover a space-bearing map in PTY.

[P1] Mirror aliases from the map they actually share

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:796

When main and vicmd have different submit widgets and a custom map aliases main,
both source maps have already been rebound when the custom map is scanned. This loop
copies the first saved entry matching the key, without checking which physical map the
custom name aliases. On zsh 5.9 at this head, bindkey -A main review-alias stores
_vicmd_submit for review-alias. Every pass-through Enter in that map then invokes
the wrong user widget, which can apply mode-specific edits or validation.

Possible direction: capture each logical map's binding before any rebind, then add a PTY
regression with distinct main/vicmd widgets and a custom alias of main.

@SunnyQjm

SunnyQjm commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 97f275c
reviewed_at: 2026-08-20T04:14:07Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • 变更范围:本 head(97f275cb)相对上轮已评审 head(86e37c72)的增量为 round 8 修复——mount 的 keymap claim 集合从 main/vicmd 泛化为 main + vicmd + bindkey -l 报告的所有非内建 keymap(zsh_marker.sh 末尾约 +13 行),并新增锚点测试 shell_host_zsh_missing_path_custom_keymap_still_intercepts。增量全部落在既有 shell_host owner 文件内,无文件增删。
  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 622 → 10 行、新增 zsh_marker.sh 833 行,均为 diff 精确值)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(standard.md 明确自然语言意图分类归 shell_host/input_intent 而非 input/),分类器本体复用 owner 内 input_intent.sh,单一事实源不变;shell_host 下新增 .sh asset 有 input_intent.sh 先例。slash/registry.rs:486:521mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host)。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本(round 8 的 bindkey -l keymap 枚举 claim 亦在 .sh asset 内),复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 833 行(diff 精确值;704 → 769 → 800 → 820 → 833 逐轮增长)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:新 .sh 首行为空行,与旧 raw string 以换行开头一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +1368 行(hunk 位于文件末尾,改后约 4280 行,diff 推算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。

Open Questions / 剩余风险

  • 当前 head 上新增两条行为级 P1(kongche-jbw 2026-08-20,zsh_marker.sh:818 自定义 keymap 名含空格被未加引号的命令替换分词导致漏 claim、:796 alias 镜像未区分物理共享来源可能拷贝错误 keymap 的条目);BryanHeBY 指出的 :655-658 注释事实错误(DEBUG_BEFORE_CMD + ERR_EXIT 可 veto 执行,注释仍断言 "its DEBUG trap cannot veto execution")在本 head 的 diff 文本中仍未改写。均属行为/注释层而非组织问题,超出本结构评审范围,但合并前须由作者闭环;修复预计仍落在 .sh asset 内,无组织结构变化。
  • _cosh_should_intercept_missing_path 与 bash verdict 并行维护的既定风险仍在;round 6/7 的 whence -w、literal-word whitelist 为 zsh-only 显式发散(zsh wrapper 在 parse 前触发,bash hook 在 resolution 失败后触发),后续 gate 语义变化需重新评估 bash 对侧是否存在同源问题。
  • round 8 的 keymap claim 泛化是 mount 期一次性扫描,mount 后新建的 keymap 仍属已声明的 post-mount rebind fail-open 边界,无新增结构风险。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照中本 head 的 CI(Test cosh-ngTest cosh-ng fast checksBuild cosh-ng releasePR ChecksCommit Message Lint,2026-08-20 完结)均 SUCCESS;行数/阈值类判定以 CI gate 实测为准,上文 .rs/.sh 行数为 diff 精确值,测试文件行数为 diff 推算。
  • PR 自述 51 个 zsh-filter shell_host 测试、clippy -D warnings、fmt、check-layout.shcheck-test-inventory.sh 通过,无法独立核实。

…ompts

Problem: zsh executes a slash-bearing command word as a path without
invoking command_not_found_handler, so the natural-language classifier
hooked there is unreachable. A prompt like
"你读一下,并安装这个skill:/usr/share/.../SKILL.md" (full-width
punctuation keeps the whole line one IFS token) surfaces as a raw
"zsh: no such file or directory" error instead of being taken over by
the agent (#1943). The bash sibling was fixed in #1942 via the extdebug
DEBUG-trap veto, but zsh's DEBUG trap has no veto-execution semantics,
so that mechanism cannot be ported.

Fix (zsh-only, per SDD cosh-1943-zsh-slash-nl-intercept):
- marker/zsh.rs: new _cosh_should_intercept_missing_path helper with
  the same five-gate verdict as the bash fix (slash-bearing first word,
  AI enabled, path provably ENOENT, missing-path classification says
  natural_language), reusing the shared input_intent.sh classifier
  verbatim (single source of truth, zero classifier changes).
- marker/zsh.rs: a _cosh_accept_line ZLE widget mounted over the
  active accept-line (alias-saving chain keeps user widgets working)
  evaluates the gates at line submission — the only pre-execution seam
  zsh offers. On a natural_language verdict it re-adds the line to
  history (print -sr; skipped for secret-bearing lines), re-echoes the
  prompt+text (ZLE erases the edit line on buffer clear; sensitive
  lines re-echo the redaction placeholder), emits the same intercept
  marker shape as the bash missing-path route, clears the buffer and
  accepts the empty line. Every gate failure and internal error falls
  open to the original accept-line: worst case is the interception not
  firing, never a broken native line. CONTEXT!=start submissions
  (PS2/heredoc continuations, vared) always pass through.
- bash side and input_intent.sh are untouched (zero diff).

Tests: five zsh-side cases mirroring the bash anchors — positive
intercept, sensitive intercept with journal redaction, fail-closed
counterproofs (existing path, English typo path, dangling symlink,
permission-opaque parent), user accept-line widget compatibility, and
heredoc continuation pass-through.

Review round 1 (Codex P1/P2 + layout gate):
- P1: the manual history re-add now replays the active history policy
  (HIST_IGNORE_SPACE and the zshaddhistory hook chain) before print -sr,
  so explicit exclusions and user hooks keep vetoing persistence; any
  veto skips the add (worst case: non-recallable intercepted prompt).
- P2: the accept-line save uses an unconditional zle -A alias, so
  customizations aliased to another builtin (not only user:* widgets)
  are preserved through the dispatch chain.
- Layout: the zsh marker script body moved verbatim to zsh_marker.sh
  (include_str!), keeping zsh.rs under the 700-line gate; slash
  registry tests re-anchored to the script file. Emitted protocol is
  byte-identical.

Review round 2 (kongche-jbw, two blocking P1s reproduced on PTY):
- A successful intercept now finalizes through the builtin
  .accept-line: a saved user widget may synthesize a command for an
  empty buffer, which would execute a native line the marker already
  claimed as intercepted. The saved widget stays on pass-through paths.
- The history policy check no longer replays foreign zshaddhistory
  hooks: outside native hook processing zsh does not restore fc -p
  history contexts and status-2 semantics cannot be honored, so any
  foreign hook (or standalone zshaddhistory function) fails closed to
  skipping the manual re-add; only cosh's own filter is consulted.

Review round 3 (kongche-jbw, 2 P1 + 2 P2; two domains hit the
review circuit-breaker after repeated counter-examples, so both were
redesigned instead of point-fixed):
- URL gate: a scheme-shaped first word (https://...) proves missing in
  a readable cwd and classifies as natural language, so the gate now
  vetoes [a-zA-Z]*://* shapes before any filesystem probe.
- Submission is modeled on keymap bindings, not the accept-line name:
  the mount claims ^M/^J across main/emacs/viins/vicmd when they bind
  another widget, remembers the original per key, and the pass-through
  dispatch routes $KEYS back to it. Other keys binding accept variants
  are an explicit non-goal (fail-open). Requires the self-named
  zle -N _cosh_accept_line registration.
- History replay is abandoned entirely: intercepted lines are never
  re-added (options x hooks x fc -p contexts form an open-ended
  bypass surface; three rounds each found a new one).
- The re-echo defers to the next precmd as plain text: zle -I repaint
  and %-expansion both run PROMPT_SUBST command substitutions an
  extra time, so the intercept route now touches no prompt rendering.

Review round 4 (kongche-jbw P1): the submit-key delegation table is
now keyed per keymap (${KEYMAP}:${KEYS}) — a flat per-key table let a
vicmd-specific widget overwrite the insert-mode entry, so Enter in the
insert map invoked the vicmd widget. Probe evidence: at dispatch time
ZLE reports the active insert map as "main" (emacs/viins resolve
through it) and vicmd by name, so the claim scans exactly those two
keymaps; a mode-specific widget is never invoked from the other mode.

Review round 5 (kongche-jbw P1): delegated dispatch guards against
re-entrant accept-line — a directly bound widget may legitimately
finish with the NAMED `zle accept-line`, which re-enters the wrapper
while $KEYS still matches the claimed key and would re-dispatch the
same widget until the nested-function limit. The in-progress flag
(cleared by an always block on every exit path) routes the re-entrant
call straight to the builtin: the user's widget runs exactly once per
submission and the line still submits.

Review round 6 (kongche-jbw): the gate now mirrors zsh command-word
resolution instead of probing only the filesystem — `whence -w` keeps
anything zsh itself resolves (aliases, functions) on the native path,
and a first word carrying unquoted glob metacharacters conservatively
stays native because the literal token proving missing on disk proves
nothing about its expansion. Unlike bash, whose hook fires after
resolution already failed, this wrapper runs before zsh parses the
line, so the gate must reproduce that resolution order. Delegated
dispatch re-entry now reaches the saved accept-line target first (a
user accept-line wrapper stays in the A -> B chain) and only a
second-level re-entry terminates on the builtin.

Review round 7 (kongche-jbw): the gate's raw first word has seen no
lexing or expansion — quoting, escaping and brace expansion can all
rewrite the word before zsh resolves it, so probing the literal text
proves nothing. The round-6 glob veto generalizes into a literal-word
whitelist: only a word made purely of non-syntax characters
(alphanumerics, `_./-`, multibyte text) may continue; any other ASCII
character fails open to the native path. The submit-key claim scan now
mirrors saved entries across aliased keymaps (`bindkey -A main vicmd`
shares one physical map, so the second scan would otherwise skip the
key and silently replace the user's widget on dispatch).

Review round 8 (kongche-jbw): an rcfile may create its own keymap,
bind the submit keys there, and select it from zle-line-init via
`zle -K` — the previous main/vicmd claim enumeration never saw it, so
such submissions bypassed the wrapper entirely. zsh's builtin keymap
set is closed, so the claim set generalizes to main, vicmd, and every
non-builtin keymap reported by `bindkey -l`; the modal builtins stay
unclaimed because their submit keys do not accept a command line. The
alias mirror now consults every saved table entry instead of only
main/vicmd.

Review round 9 (kongche-jbw): keymap names may carry spaces, so the
claim enumeration splits `bindkey -l` per line instead of by IFS; and
the single-pass alias mirror could copy the wrong source when main and
vicmd carry different widgets, so the mount is now two-phase — phase 1
captures every keymap's original binding before any physical map is
touched (an aliased map reads the true widget of the map it shares),
phase 2 rebinds every captured key. The mirror heuristic is removed
entirely; table-key splitting is fixed-length from the tail because
keymap names may also contain colons.

Assisted-by: Qoder:1.22.0
Signed-off-by: SunnyQjm <mfeng@linux.alibaba.com>
@SunnyQjm
SunnyQjm force-pushed the fix/cosh-1943-zsh-slash-nl-intercept branch from 97f275c to 026f6e9 Compare August 20, 2026 05:38
@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

Review round 9 addressed in 026f6e9f (amend + rebase). Both items verified and accepted; the alias fix replaces the mirror design outright rather than patching its source selection.

[P1] Preserve whitespace in custom keymap names — valid — the unquoted command substitution split bindkey -l output by IFS, so bindkey -N 'review map' main was never claimed. The enumeration now splits per line (${(f)...}); the phase-2 table-key split is fixed-length from the tail since keymap names may also contain colons while the submit key is always a single character.

[P1] Mirror aliases from the map they actually share — valid — the round-7/8 single-pass mirror could not know which physical map an alias shares (associative-array iteration order is unspecified), so with distinct main/vicmd widgets it copied the wrong source. The mount is now two-phase, following the direction you offered back in round 7: phase 1 captures every claimed keymap's original binding while no physical map has been rebound — an aliased map therefore reads the true widget of the map it shares, with no scan-order dependence — and phase 2 rebinds every captured key (idempotent on shared maps). The mirror heuristic is deleted entirely.

Evidence: two new PTY tests —

  • shell_host_zsh_missing_path_space_keymap_still_intercepts — a space-bearing map selected from zle-line-init is claimed; interception fires and the user's widget runs exactly once;
  • shell_host_zsh_missing_path_alias_mirrors_shared_map_widget — distinct main/vicmd widgets plus bindkey -A main review-alias: the aliased map dispatches _main_submit exactly once and the vicmd widget log stays empty.

Container probe additionally confirms phase-1 capture reads review-alias:^M = _main_submit (the previous mirror picked _vicmd_submit on this head). Anti-regression: with the fix reverted (tests kept) both fail; restored, the zsh-filter suite is 53/53 green. Workspace clippy -D warnings, fmt, check-layout.sh, check-test-inventory.sh all green on latest main.

SHA
Reviewed (round 9) 97f275cb
Current head 026f6e9f

@SunnyQjm
SunnyQjm requested a review from kongche-jbw August 20, 2026 05:38
@SunnyQjm

SunnyQjm commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

PR number: #2210
head_sha: 026f6e9
reviewed_at: 2026-08-20T06:17:29Z

评审结论

Findings

未发现 blocking package/module/public API 组织问题。

结构核对

  • 变更范围:本 head(026f6e9f)与本 automation 2026-08-20 06:05 已评审 head 相同,diff 无变化;新增动态为 Test cosh-ng 于 06:11:13 完结 SUCCESS,前轮 Open Question 中的 CI 未定项已闭环(作者 triage 归因的 provider_lifecycle 超时用例未进入最终失败列表)。
  • Owner:production 变更全部落在 shell_host/marker/zsh.rs 622 → 10 行、新增 zsh_marker.sh 834 行,均为 diff 精确值)。zsh 侧 slash-bearing 自然语言拦截属 shell_host owner 的 shell 级输入意图分类职责(standard.md 明确自然语言意图分类归 shell_host/input_intent 而非 input/),分类器本体复用 owner 内 input_intent.sh,单一事实源不变;shell_host 下新增 .sh asset 有 input_intent.sh 先例。slash/registry.rs:486:521mod tests 内两处 include_str! 锚点从 zsh.rs 改指 zsh_marker.sh,是脚本提取的必要配套(bash 对侧原有同款 include),不构成跨 owner 混杂。新增测试落在 tests/shell_host/marker.rs,与 bash 锚点同位置。无 root src/*.rs 新增,无 utils/common/misc
  • Public API:未触碰 lib.rs,无新增 pub mod/pub use/pubzsh_marker_script() 维持 pub(in crate::shell_host),新增 ZSH_MARKER_SCRIPT 常量为 module-private。测试仅消费已登记 internal-migration-surface 项(run_scripted_zshShellHostConfigScriptedInputbuild_command_blocksShellEventKind),未为测试新增 production 可见性。
  • 依赖方向:新增逻辑全部为 owner 内嵌 zsh 脚本(含 round 9 的 ${(f)} 按行枚举与两阶段 mount),复用既有 _cosh_classify_missing_cosh_path_provably_missing_cosh_emit_intercept_marker 等 owner 内 helper;未引入 shell_host -> agent/approval/ui/runtime 依赖,无 cosh_shell:: self-crate path。
  • 新 crate / 新依赖 / lockfile / 跨 crate:均无。

大文件 / 阈值

  • zsh.rs 622 → 10 行(diff 精确值),提取路径与 large-file-inventory 中 bash 对侧(marker/bash.rs 台账 934 行)登记的 .sh asset extraction 治理方向一致;zsh.rs 未登记台账且改后远低于阈值,无越线。
  • zsh_marker.sh 834 行(diff 精确值;704 → 769 → 800 → 820 → 833 → 834 逐轮增长)。行数阈值治理口径(check-layout.sh、large-file-inventory)针对 *.rs production 文件,.sh 静态 asset 不在其列,不构成 finding;重申前轮建议:参照 bash 台账条目补一条 .sh asset owner note 明确治理口径(非阻断建议)。
  • 字节同一性:新 .sh 首行为空行,与旧 raw string 以换行开头一致;byte-identity 由既有 golden 测试覆盖(本评审未跑)。
  • tests/shell_host/marker.rs +1518 行(hunk 位于文件末尾,改后约 4428 行,diff 推算);测试文件行数治理以 shell-test-organization 为准,本输入未含其阈值口径,不作判定。
  • Test cosh-ng fast checks(含 layout 类快检)在本 head SUCCESS(05:45)。

Open Questions / 剩余风险

  • round-10 两条行为级 P1 仍未闭环(kongche-jbw 2026-08-20 05:57,基于本 head):(a)zsh_marker.sh:788bindkey 输出按末字段切分(widget="${binding##* }"),含空格的 widget 名只记录末段,lookup 落空导致该绑定下拦截整体失效;(b)zsh_marker.sh:811 非内建 keymap 一律视为提交 map,用户自建的模态 keymap(Enter 绑定不调用 accept-line 的 widget)会被错误 claim 并改写。当前 diff 文本确认两处实现均未变化。属行为层而非组织问题,超出本结构评审范围,但合并前须由作者闭环;修复预计仍落在 .sh asset 内,无组织结构变化。
  • BryanHeBY 指出的注释事实错误(zsh_marker.sh:655-658DEBUG_BEFORE_CMD + ERR_EXIT 可 veto 执行,注释仍断言 "its DEBUG trap cannot veto execution")在本 head 的 .sh 文本中仍未改写。属注释层而非组织问题,合并前建议闭环。
  • _cosh_should_intercept_missing_path 与 bash verdict 并行维护的既定风险仍在;round 6/7/9 的 whence -w、literal-word whitelist、两阶段 mount 为 zsh-only 显式发散(zsh wrapper 在 parse 前触发,bash hook 在 resolution 失败后触发),后续 gate 语义变化需重新评估 bash 对侧是否存在同源问题。
  • round 8/9 的 keymap claim 泛化与两阶段 mount 均为 mount 期一次性扫描,mount 后新建 keymap 仍属已声明的 post-mount rebind fail-open 边界,无新增结构风险。

Validation

  • 本评审不执行工具,未跑任何验证;结构结论基于输入 diff 静态核对。
  • 输入快照中本 head CI 全部完结:Test cosh ng SUCCESS(06:11:13)、Test cosh-ng fast checks SUCCESS(05:45)、Build cosh-ng releasePR ChecksCommit Message Lintlicense/cla 均 SUCCESS。行数/阈值类判定以 CI gate 实测与 diff 精确值为准。
  • PR 自述 clippy -D warnings、fmt、check-layout.shcheck-test-inventory.sh 及 53 个 zsh-filter shell_host 测试通过,无法独立核实。

@kongche-jbw kongche-jbw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review baseline: 8f2416470233...026f6e9fac91

[P1] Preserve the complete widget name from bindkey

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:788

ZLE accepts widget names containing whitespace. For a direct binding to submit widget,
bindkey emits "^M" submit widget, but this last-field split records only widget.
The following widgets[$widget] lookup then fails, so Enter remains bound directly to the
user widget and never reaches _cosh_accept_line. A slash-bearing natural-language prompt
therefore still ends in the native path error in this valid configuration.

Possible direction: remove only the rendered key prefix, preserve the remaining widget name
exactly, and add a PTY regression with a space-bearing widget name.

[P1] Do not claim Enter in non-submitting custom maps

src/cosh-ng/crates/cosh-shell/src/shell_host/marker/zsh_marker.sh:811

Every non-builtin keymap is treated as a command-submission map, but user keymaps may be
modal. For example, a map selected by zle-line-init can bind Enter to a widget that edits
BUFFER or accepts a selection without calling accept-line. This scan still captures that
widget and phase 2 rebinds Enter to _cosh_accept_line; on a slash-bearing natural-language
buffer, the gate clears and submits the line instead of invoking the modal widget. Excluding
only zsh's builtin modal maps does not preserve equivalent user-defined maps.

Possible direction: claim custom-map bindings only with a verifiable submission contract,
fail open otherwise, and cover a non-submitting custom-map Enter widget in PTY.

@SunnyQjm

Copy link
Copy Markdown
Collaborator Author

CI triage — Test cosh-ng failures on 026f6e9f (attempts 1–3)

  • Failing case (identical in all three attempts): provider_lifecycle::cosh_core_sync_drains_child_output_while_writing_large_prompt, panicking at provider_lifecycle.rs:364 — the test's own 3-second recv_timeout on the synchronous transport thread.
  • This test is outside this PR's diff (zsh marker script, its tests, and registry anchors only). Locally on this exact head the case passes 3/3 consecutive runs in ~0.6 s (aarch64 Linux container), so the branch code drains the transport correctly; the CI runner is exceeding the 3 s budget under load. The same case timed out once on this PR back on the round-2 CI run and passed on rerun.
  • Given three consecutive CI failures I'm recording this triage before another rerun rather than silently retrying. If it persists, the 3 s budget in that test likely needs revisiting upstream — happy to file a tracking issue.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cosh-ng] zsh: slash-bearing natural-language prompts bypass interception (bash fixed in #1942)

4 participants