diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml new file mode 100644 index 0000000000..3c4cb67d7d --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md +2026-09-03-root-marker-metadata-failures.md: e31ef646d7190a1239684f7b66a118deb9ae1087 +2026-09-03-root-marker-metadata-failures.zh.md: 4b62f4e01ba56074a10dbcb3ae37c4478fe82379 diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md new file mode 100644 index 0000000000..e31ef646d7 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md @@ -0,0 +1,27 @@ +# Agent Note: Root marker metadata failures + +Status: implemented + +English | [中文](2026-09-03-root-marker-metadata-failures.zh.md) + +## Problem + +Project-root discovery probes each configured marker while walking upward from the session working directory. Treating every resolve or stat failure as a missing marker lets a permission, I/O, or provider failure continue into an ancestor project and load unrelated workspace instructions. The discovery result must distinguish confirmed absence from unavailable metadata. + +## Decision + +Root-marker discovery continues upward only when host stat reports `ENOENT` or `ENOTDIR`, or when a filesystem provider returns no stat information or reports `FS_NOT_FOUND` from resolution or stat. It rethrows every other marker error unchanged after checking cancellation. Instruction-file candidates keep their separate availability policy: resolution, stat, and read failures skip only that candidate because files can race with discovery without changing project identity. + +## Alternatives considered + +**Treat every marker failure as absence and continue upward.** Rejected because an inaccessible child directory could inherit instructions from an unrelated ancestor project while discovery reports success. + +**Stop at the first unavailable marker and use the session working directory as the root.** Rejected because it converts an unknown project root into a different project identity and can silently omit valid broader instructions. + +## Consequences + +Project-root discovery favors correct project identity over availability: one non-missing metadata failure anywhere in the ancestor walk rejects baseline loading with the original error. Instruction-file candidate failures retain their existing skip behavior. A failed baseline creates no workspace-context Session event, so the keyless recorded-session harness has no durable output for this path. + +## Verification + +Focused unit tests cover confirmed provider absence and unavailable host and provider marker metadata. The unavailable cases also prove that ancestor instructions do not enter derived model history. diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md new file mode 100644 index 0000000000..4b62f4e01b --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md @@ -0,0 +1,27 @@ +# Agent Note: 根标记元数据故障 + +Status: implemented + +[English](2026-09-03-root-marker-metadata-failures.md) | 中文 + +## 问题 + +项目根发现从会话工作目录向上遍历时,会探测每个已配置的标记。把所有 resolve 或 stat 故障都当作标记缺失,会使权限、I/O 或提供方故障越过该目录继续搜索祖先项目,并加载无关的工作区指令。发现结果必须区分确认缺失与元数据不可用。 + +## 决策 + +只有当宿主 stat 报告 `ENOENT` 或 `ENOTDIR`,或文件系统提供方未返回 stat 信息,或从解析或 stat 报告 `FS_NOT_FOUND` 时,根标记发现才会继续向上。检查取消后,其他标记错误会原样重新抛出。指令文件候选项保留独立的可用性策略:解析、stat 和读取故障只会跳过该候选项,因为文件可能与发现过程发生竞争,而不会改变项目身份。 + +## 考虑过的替代方案 + +**把所有标记故障都当作缺失并继续向上。** 不予采用,因为无法访问的子目录可能继承无关祖先项目中的指令,而发现过程仍报告成功。 + +**在第一个不可用标记处停止,并把会话工作目录用作根目录。** 不予采用,因为这会把未知的项目根转换为另一个项目身份,并可能静默省略有效的更宽泛指令。 + +## 后果 + +项目根发现优先保证项目身份正确,而非可用性:祖先遍历中任何不是缺失的元数据故障都会使基线加载以原始错误拒绝。指令文件候选项故障保留现有的跳过行为。失败的基线不会创建工作区上下文 Session event,因此无密钥录制会话 harness 没有可用于该路径的持久输出。 + +## 验证 + +聚焦单元测试覆盖确认的提供方缺失,以及不可用的宿主与提供方标记元数据。不可用情况还证明祖先指令不会进入派生模型历史。 diff --git a/packages/context/agent-instructions/README.i18n.yaml b/packages/context/agent-instructions/README.i18n.yaml index 1323a03a91..4f3b7417ea 100644 --- a/packages/context/agent-instructions/README.i18n.yaml +++ b/packages/context/agent-instructions/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/agent-instructions/README.md -README.md: 114202cdd7cd4d2fd1d2c82330d4f373a3735b0d -README.zh.md: 029f87d3b53317b198c011829faf85b41e32c9b3 +README.md: ffde23b2144109d69da6070225999936e03bdd91 +README.zh.md: 0d1313f2d72bb80f73e633ed75b6b7b5b1f82062 diff --git a/packages/context/agent-instructions/README.md b/packages/context/agent-instructions/README.md index 114202cdd7..ffde23b214 100644 --- a/packages/context/agent-instructions/README.md +++ b/packages/context/agent-instructions/README.md @@ -35,7 +35,7 @@ The first request includes one durable baseline message with the user-global `$D The defaults suit a typical checkout: `.git` marks the project root, `AGENTS.md` and `CLAUDE.md` are the base candidates, and `AGENTS.local.md` and `CLAUDE.local.md` are additive local overlays. Only `maxBytes` is required — it caps the complete rendered baseline so each deployment chooses its prompt budget explicitly. -Root discovery climbs only when a marker probe confirms that the marker is absent. A permission or I/O failure stops discovery and surfaces the host or filesystem-provider error instead of selecting an ancestor project. +Root discovery climbs only when a marker probe confirms that the marker is absent. A permission or I/O failure stops discovery and surfaces the host or filesystem-provider error instead of selecting an ancestor project. The [root-marker metadata decision](../../../.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md) records why discovery fails instead of substituting another root. ```yaml - name: '@deepseek-ai/dsh-agent-instructions' diff --git a/packages/context/agent-instructions/README.zh.md b/packages/context/agent-instructions/README.zh.md index 029f87d3b5..0d1313f2d7 100644 --- a/packages/context/agent-instructions/README.zh.md +++ b/packages/context/agent-instructions/README.zh.md @@ -35,7 +35,7 @@ kind: "package-reference" 默认设置适合典型检出:`.git` 标记项目根目录,`AGENTS.md` 与 `CLAUDE.md` 是基础候选,`AGENTS.local.md` 与 `CLAUDE.local.md` 是叠加的本地 overlay。只有 `maxBytes` 必填——它限制完整渲染后的基线,让每个部署显式选择自己的提示词预算。 -只有确认项目根标记不存在时,项目根发现才会继续上溯。权限或 I/O 失败会停止发现,并返回 Host 或文件系统提供方的错误,而不会选择祖先项目。 +只有确认项目根标记不存在时,项目根发现才会继续上溯。权限或 I/O 失败会停止发现,并抛出宿主或文件系统提供方的原始错误,而不会选择祖先项目。[根标记元数据决策](../../../.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md)说明发现为何必须失败,而不能替换为其他根目录。 ```yaml - name: '@deepseek-ai/dsh-agent-instructions' diff --git a/packages/context/agent-instructions/src/files.ts b/packages/context/agent-instructions/src/files.ts index 32388aeb86..b91cab12a1 100644 --- a/packages/context/agent-instructions/src/files.ts +++ b/packages/context/agent-instructions/src/files.ts @@ -319,6 +319,8 @@ async function discoverInstructionFiles( * duplicates are collapsed later, once content is read. * @param options - cwd, home, root marker, and candidate configuration. * @returns path-deduplicated instruction candidates in model precedence order. + * @throws the original root-marker metadata error or cancellation reason when + * discovery cannot identify the project root. */ export async function discoverBaselineInstructionFiles(options: DiscoverOptions): Promise { return (await discoverInstructionFiles(options)).map(({ absolutePath, displayPath }) => ({ absolutePath, displayPath })) @@ -393,6 +395,8 @@ export function dedupInstructionFilesByDirectory(files: LoadedInstructionFile[]) * @param options - discovery, source-size, byte-budget, and cancellation configuration. * @param fileSystem - optional provider used instead of host filesystem reads. * @returns rendered baseline context, or undefined when nothing can be loaded. + * @throws the original root-marker metadata error or cancellation reason when + * discovery cannot identify the project root. */ export async function loadBaselineInstructions( options: LoadOptions,