mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-05 17:27:49 +02:00
fix: harden default co-author opt-out and correct the docs
Follow-up on the co-author default in this PR. - Remove the existsSync/writeFileSync race in the installer settings write (CodeQL js/file-system-race, high). A single guarded read now covers the fresh-install case, and unreadable or non-object settings are left untouched. - Respect `attribution` as an explicit user choice. It supersedes `includeCoAuthoredBy` in Claude Code 2.1.x, so a user who configured it would otherwise have had a dead key written into their settings. - Share one opt-out rule via scripts/lib/claude-commit-attribution.js instead of duplicating it across the installer and plugin setup. - Update the git-workflow rule and its nine mirrors and translations, which still told users ECC does not ship this setting. We keep writing the deprecated `includeCoAuthoredBy` key rather than `attribution`: unknown keys fail Claude Code settings validation, so writing `attribution` would break users on older versions.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
// Claude Code appends a `Co-Authored-By` trailer to commits and PRs unless the
|
||||
// user opts out, so ECC-managed installs default that off.
|
||||
//
|
||||
// Two settings control the trailer. `attribution: { commit, pr }` is the current
|
||||
// one and wins when set; `includeCoAuthoredBy` is deprecated as of Claude Code
|
||||
// 2.1.x but still honored, and is the only one older versions understand. We
|
||||
// write the deprecated key because unknown keys fail settings validation, so
|
||||
// writing `attribution` would break users on older Claude Code. Either key being
|
||||
// present counts as a deliberate user choice that ECC must not overwrite.
|
||||
const COAUTHOR_SETTING_KEY = 'includeCoAuthoredBy';
|
||||
|
||||
function hasExplicitCommitAttributionPreference(settings) {
|
||||
if (!settings || typeof settings !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (typeof settings[COAUTHOR_SETTING_KEY] === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const attribution = settings.attribution;
|
||||
return Boolean(attribution)
|
||||
&& typeof attribution === 'object'
|
||||
&& !Array.isArray(attribution)
|
||||
&& (attribution.commit !== undefined || attribution.pr !== undefined);
|
||||
}
|
||||
|
||||
function withCommitAttributionDisabled(settings) {
|
||||
if (hasExplicitCommitAttributionPreference(settings)) {
|
||||
return settings;
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
[COAUTHOR_SETTING_KEY]: false,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
COAUTHOR_SETTING_KEY,
|
||||
hasExplicitCommitAttributionPreference,
|
||||
withCommitAttributionDisabled,
|
||||
};
|
||||
@@ -5,6 +5,10 @@ const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const { writeFileAtomic } = require('./atomic-write');
|
||||
const {
|
||||
hasExplicitCommitAttributionPreference,
|
||||
withCommitAttributionDisabled,
|
||||
} = require('./claude-commit-attribution');
|
||||
const { normalizeGitHubGitOrigin } = require('./github-origin');
|
||||
const {
|
||||
CURRENT_PLUGIN_ID,
|
||||
@@ -18,7 +22,6 @@ const OFFICIAL_MARKETPLACE_NAME = 'ecc';
|
||||
const OFFICIAL_MARKETPLACE_REPO = 'affaan-m/ecc';
|
||||
const OFFICIAL_MARKETPLACE_URL = 'https://github.com/affaan-m/ECC';
|
||||
const PROVIDER_COMMAND_TIMEOUT_MS = 120 * 1000;
|
||||
const CLAUDE_COAUTHOR_SETTING_KEY = 'includeCoAuthoredBy';
|
||||
const VALID_SCOPES = new Set(['user', 'project', 'local']);
|
||||
const VALID_HOOK_MODES = new Set(['off', 'minimal', 'standard', 'strict']);
|
||||
|
||||
@@ -322,18 +325,11 @@ function deriveHookMode(settings) {
|
||||
}
|
||||
|
||||
function withClaudeCommitAttributionPreference(settings) {
|
||||
if (settings?.[CLAUDE_COAUTHOR_SETTING_KEY] === true) {
|
||||
return settings;
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
[CLAUDE_COAUTHOR_SETTING_KEY]: false,
|
||||
};
|
||||
return withCommitAttributionDisabled(settings);
|
||||
}
|
||||
|
||||
function needsClaudeCommitAttributionPreferenceWrite(settings) {
|
||||
return settings?.[CLAUDE_COAUTHOR_SETTING_KEY] !== false
|
||||
&& settings?.[CLAUDE_COAUTHOR_SETTING_KEY] !== true;
|
||||
return !hasExplicitCommitAttributionPreference(settings);
|
||||
}
|
||||
|
||||
function writeClaudePluginOptions(settingsPath, hooks) {
|
||||
|
||||
@@ -4,6 +4,10 @@ const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
hasExplicitCommitAttributionPreference,
|
||||
withCommitAttributionDisabled,
|
||||
} = require('../claude-commit-attribution');
|
||||
const { writeInstallState } = require('../install-state');
|
||||
const { filterMcpConfig, parseDisabledMcpServers } = require('../mcp-config');
|
||||
const { assertWithinTrustedRoot } = require('../path-safety');
|
||||
@@ -120,26 +124,32 @@ function shouldSetClaudeCommitAttributionPreference(plan) {
|
||||
}
|
||||
|
||||
function writeClaudeCommitAttributionPreference(settingsPath) {
|
||||
let settings = {};
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
try {
|
||||
settings = readJsonObject(settingsPath, 'Claude settings');
|
||||
} catch (_error) {
|
||||
// Read once rather than probing with existsSync first. Checking for the file and
|
||||
// then writing it is a file system race (CodeQL js/file-system-race), and a
|
||||
// missing file is simply the fresh-install case.
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
// Unreadable or malformed settings belong to the user; leave them untouched.
|
||||
return false;
|
||||
}
|
||||
settings = {};
|
||||
}
|
||||
|
||||
if (settings.includeCoAuthoredBy === true) {
|
||||
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasExplicitCommitAttributionPreference(settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
settingsPath,
|
||||
formatJson({
|
||||
...settings,
|
||||
includeCoAuthoredBy: false,
|
||||
}),
|
||||
formatJson(withCommitAttributionDisabled(settings)),
|
||||
'utf8'
|
||||
);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user