Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/server/src/policy/ManagedPolicy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,19 @@ it.layer(NodeServices.layer)("ManagedPolicy.load", (it) => {
},
},
}),
// Startup reads OTLP endpoints before the policy, and environment variables outrank them.
JSON.stringify({
...base,
settings: { "observability.otlpTracesUrl": { value: "", locked: true } },
}),
],
(contents) =>
loadFrom(contents).pipe(
Effect.flip,
Effect.map((error) => error.reason),
),
);
assert.lengthOf(reasons, 13);
assert.lengthOf(reasons, 14);
assert.include(reasons[3], "storageCleanup.logsAfterDayz");
assert.include(reasons[6], "adds entries users could not remove");
assert.include(reasons[7], "adds entries users could not remove");
Expand All @@ -158,6 +163,7 @@ it.layer(NodeServices.layer)("ManagedPolicy.load", (it) => {
assert.include(reasons[10], 'lock "worktreeCleanup" too');
assert.include(reasons[11], 'lock "providerInstances.codex" as a whole');
assert.include(reasons[12], "holds a credential");
assert.include(reasons[13], "a policy cannot override");
}),
);

Expand Down
13 changes: 13 additions & 0 deletions apps/server/src/policy/managedSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ const UNMANAGEABLE_KEYS: ReadonlySet<string> = new Set([
"projectScriptOverrides",
]);

/**
* Startup resolves OTLP endpoints from the settings file and desktop bootstrap input before the policy
* loads, and the user's OTLP environment variables outrank them, so a managed value would look
* enforced without being.
*/
const ENVIRONMENT_OVERRIDDEN_KEYS: ReadonlySet<string> = new Set(["observability"]);

/** Legacy settings the server derives from another on every write; manage that one instead. */
const DERIVED_FROM: ReadonlyMap<string, string> = new Map([
["compactIdleThreads", "idleCompaction"],
Expand Down Expand Up @@ -253,6 +260,12 @@ export function makeManagedSettings(
if (!(head in ServerSettings.fields) || UNMANAGEABLE_KEYS.has(head)) {
return { _tag: "Invalid", reason: `"${path}" is not a setting this version can manage` };
}
if (ENVIRONMENT_OVERRIDDEN_KEYS.has(head)) {
return {
_tag: "Invalid",
reason: `"${path}" is resolved at startup from each user's environment, settings file and desktop bootstrap input, which a policy cannot override`,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
const source = DERIVED_FROM.get(head);
if (source !== undefined) {
return {
Expand Down
2 changes: 2 additions & 0 deletions docs/user/managed-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ A few settings need care:
- `worktreeCleanup`, which projects can also set, overrides the `worktree` rules in
`storageCleanup`. Lock it along with those rules; T3 Code refuses a policy that locks only the
rules.
- `observability` cannot be managed. Its OTLP endpoints come from each user's environment
variables, settings file and desktop startup input, which a policy cannot override.
- Manage `idleCompaction`, not the older `compactIdleThreads`. A Claude instance's own idle
compaction choice still wins over `idleCompaction`.
- A provider instance's fields override each other, so lock `providerInstances.<id>` as a whole,
Expand Down
Loading