From 5469461b8d47d38cdab5b6b42eaf76d397feb42f Mon Sep 17 00:00:00 2001 From: maco Date: Sun, 20 Sep 2026 09:28:40 +0000 Subject: [PATCH 1/2] fix(server): skip duplicate CLAUDE.md in repo conventions When CLAUDE.md is a symlink or byte-identical copy of AGENTS.md, repository-conventions text generation sent the same instructions twice to Claude writers. Skip the CLAUDE.md block when the read content matches AGENTS.md. Done with Grok 4.6 in Cursor. Co-authored-by: maco --- apps/server/src/git/GitManager.test.ts | 94 ++++++++++++++++++++++++++ apps/server/src/git/GitManager.ts | 7 +- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/apps/server/src/git/GitManager.test.ts b/apps/server/src/git/GitManager.test.ts index 6837d849d779..a3c21379b61f 100644 --- a/apps/server/src/git/GitManager.test.ts +++ b/apps/server/src/git/GitManager.test.ts @@ -2949,6 +2949,100 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }), ); + it.effect("sends identical AGENTS.md and CLAUDE.md content to a Claude writer once", () => + Effect.gen(function* () { + const repoDir = yield* makeTempDir("t3code-git-manager-"); + yield* runGit(repoDir, ["init", "--initial-branch=main"]); + yield* runGit(repoDir, ["config", "user.email", "test@example.com"]); + yield* runGit(repoDir, ["config", "user.name", "Test User"]); + const instructions = "Use lowercase source control text."; + // A byte-identical copy stands in for the common `CLAUDE.md -> AGENTS.md` + // symlink: both read to the same string, which is what the dedup compares. + NodeFS.writeFileSync(NodePath.join(repoDir, "AGENTS.md"), instructions); + NodeFS.writeFileSync(NodePath.join(repoDir, "CLAUDE.md"), instructions); + NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\n"); + yield* runGit(repoDir, ["add", "README.md"]); + let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined; + + const { manager } = yield* makeManager({ + serverSettings: { + textGenerationModelSelection: { + instanceId: ProviderInstanceId.make("claudeAgent"), + model: "claude-sonnet-4-6", + }, + sourceControlWritingStyle: { + mode: "repo_conventions" as const, + }, + }, + textGeneration: { + generateCommitMessage: (input) => { + generatedPolicy = input.policy; + return Effect.succeed({ subject: "Create initial commit", body: "" }); + }, + }, + }); + yield* runStackedAction(manager, { + cwd: repoDir, + action: "commit", + }); + + expect(generatedPolicy?.kind).toBe("repo_conventions"); + expect(generatedPolicy?.inferRepositoryConventions).toBe(true); + for (const field of [ + generatedPolicy?.commitInstructions, + generatedPolicy?.changeRequestInstructions, + ]) { + expect(field).toContain(`Local AGENTS.md:\n${instructions}`); + expect(field).not.toContain("Local CLAUDE.md:"); + expect(field?.split(instructions).length).toBe(2); + } + }), + ); + + it.effect("omits CLAUDE.md from repository conventions for a non-Claude writer", () => + Effect.gen(function* () { + const repoDir = yield* makeTempDir("t3code-git-manager-"); + yield* runGit(repoDir, ["init", "--initial-branch=main"]); + yield* runGit(repoDir, ["config", "user.email", "test@example.com"]); + yield* runGit(repoDir, ["config", "user.name", "Test User"]); + const agentInstructions = "Use lowercase source control text."; + const claudeInstructions = "Keep pull request bodies brief."; + NodeFS.writeFileSync(NodePath.join(repoDir, "AGENTS.md"), agentInstructions); + NodeFS.writeFileSync(NodePath.join(repoDir, "CLAUDE.md"), claudeInstructions); + NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\n"); + yield* runGit(repoDir, ["add", "README.md"]); + let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined; + + const { manager } = yield* makeManager({ + serverSettings: { + sourceControlWritingStyle: { + mode: "repo_conventions" as const, + }, + }, + textGeneration: { + generateCommitMessage: (input) => { + generatedPolicy = input.policy; + return Effect.succeed({ subject: "Create initial commit", body: "" }); + }, + }, + }); + yield* runStackedAction(manager, { + cwd: repoDir, + action: "commit", + }); + + expect(generatedPolicy?.kind).toBe("repo_conventions"); + expect(generatedPolicy?.commitInstructions).toContain( + `Local AGENTS.md:\n${agentInstructions}`, + ); + expect(generatedPolicy?.commitInstructions).not.toContain("Local CLAUDE.md:"); + expect(generatedPolicy?.changeRequestInstructions).toContain( + `Local AGENTS.md:\n${agentInstructions}`, + ); + expect(generatedPolicy?.changeRequestInstructions).not.toContain("Local CLAUDE.md:"); + }), + ); + it.effect("uses custom commit message when provided", () => Effect.gen(function* () { const repoDir = yield* makeTempDir("t3code-git-manager-"); diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index 033cc192ccfe..d8d34a4354e5 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -755,9 +755,14 @@ export const make = Effect.gen(function* () { provider.instanceId === settings.modelSelection.instanceId && provider.driver === "claudeAgent", ); - const claudeInstructions = isClaudeWriter + const claudeInstructionsRead = isClaudeWriter ? yield* readRepositoryInstructions(cwd, "CLAUDE.md") : ""; + // CLAUDE.md is commonly a symlink to, or a copy of, AGENTS.md. Both + // reads then return the same text, and the writer would be sent it + // twice. Comparing content covers symlinks, hardlinks and copies. + const claudeInstructions = + claudeInstructionsRead === agentInstructions ? "" : claudeInstructionsRead; const examples = [ ...(subjects.length > 0 ? [["Recent commit subjects from this repository:", ...subjects].join("\n")] From 382b95b8547406528def5999a549a73390e02b81 Mon Sep 17 00:00:00 2001 From: maco Date: Sun, 20 Sep 2026 09:32:15 +0000 Subject: [PATCH 2/2] test(server): assert full convention policy for CLAUDE.md dedupe Match the existing GitManager policy fixture so the identical-content and non-Claude cases typecheck. Done with Grok 4.6 in Cursor. Co-authored-by: maco --- apps/server/src/git/GitManager.test.ts | 31 ++++++++++---------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/apps/server/src/git/GitManager.test.ts b/apps/server/src/git/GitManager.test.ts index a3c21379b61f..c92649dcf96d 100644 --- a/apps/server/src/git/GitManager.test.ts +++ b/apps/server/src/git/GitManager.test.ts @@ -2986,16 +2986,12 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { action: "commit", }); - expect(generatedPolicy?.kind).toBe("repo_conventions"); - expect(generatedPolicy?.inferRepositoryConventions).toBe(true); - for (const field of [ - generatedPolicy?.commitInstructions, - generatedPolicy?.changeRequestInstructions, - ]) { - expect(field).toContain(`Local AGENTS.md:\n${instructions}`); - expect(field).not.toContain("Local CLAUDE.md:"); - expect(field?.split(instructions).length).toBe(2); - } + expect(generatedPolicy).toEqual({ + kind: "repo_conventions", + commitInstructions: `Follow the repository's established commit message style when examples are available.\n\nLocal AGENTS.md:\n${instructions}`, + changeRequestInstructions: `Follow the repository's established change request title and body style when examples are available.\n\nLocal AGENTS.md:\n${instructions}`, + inferRepositoryConventions: true, + }); }), ); @@ -3031,15 +3027,12 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { action: "commit", }); - expect(generatedPolicy?.kind).toBe("repo_conventions"); - expect(generatedPolicy?.commitInstructions).toContain( - `Local AGENTS.md:\n${agentInstructions}`, - ); - expect(generatedPolicy?.commitInstructions).not.toContain("Local CLAUDE.md:"); - expect(generatedPolicy?.changeRequestInstructions).toContain( - `Local AGENTS.md:\n${agentInstructions}`, - ); - expect(generatedPolicy?.changeRequestInstructions).not.toContain("Local CLAUDE.md:"); + expect(generatedPolicy).toEqual({ + kind: "repo_conventions", + commitInstructions: `Follow the repository's established commit message style when examples are available.\n\nLocal AGENTS.md:\n${agentInstructions}`, + changeRequestInstructions: `Follow the repository's established change request title and body style when examples are available.\n\nLocal AGENTS.md:\n${agentInstructions}`, + inferRepositoryConventions: true, + }); }), );