Managed-settings interim fail-closed uses an empty allow list ([[]]) and permanently drops user MCP servers that register inside the window — reproduces via the desktop app, on an account with no managed policy
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Shell
- Star
- 11.2k
- Fork
- 1.9k
- Merge trung bình
- 14 giờ 16 phút
- Pull request đã merge (30 ngày)
- 6
Mô tả
Describe the bug
While the CLI resolves managed settings, it installs an interim "deny everything" MCP policy
(managedAllowedMcpServerLists: [[]] — one list, containing nothing). Any user-configured MCP server
that happens to register during that window is rejected with:
[ERROR] Skipping MCP server "<name>": not permitted by enterprise managed allow list
Two things make this a real bug rather than a benign transient:
- It fires even when there is no managed policy at all. In my logs the resolution completes in
5–65 ms withsource=none,serverFetchFailed=false, and
[managedSettings] applied: no bypass restriction in force (managed policy absent). Nothing failed,
and there is no enterprise policy on this account. The deny is purely pre-emptive. - Servers denied during that window are never restored. The "policy relaxed" restore loop replays
only servers captured inpolicyRemovedConfigs, and the startup deny paths never write there. Those
servers stay dead for the entire life of the session.
Net effect: opening a session while any session is resolving managed settings silently produces a
session with no user MCP servers, permanently. Built-in servers are exempt (the trusted-first-party
check keeps entries whose source === "builtin"), so it presents as "my servers are broken" rather than
"a policy was applied".
Important scope note
The defect is in the CLI, but it is only reachable when the CLI is driven by a client that sets
enableManagedSettings: true. The GitHub Copilot desktop app sets it on every session.create and
session.resume. A CLI launched by hand does not, so this cannot be reproduced from a bare terminal
invocation — please see the warning at the top of the repro steps before trying.
This is provable from both sides of the interface:
- CLI side (
app.js, 1.0.79-5): 21 occurrences ofenableManagedSettings, and not one assigns it
true. BothenableManagedSettings=!0andenableManagedSettings:!0are absent from the entire
bundle. The field initialises to!1and is thereafter only read from the client payload
(this.enableManagedSettings = n.enableManagedSettings ?? !1). Every managed-settings entry point is
gated on it, includingrefreshManagedSettings(), which opens with
if(!this.enableManagedSettings)return;. - App side (
copilot-sdk/index.js, app 1.1.6): passes
enableManagedSettings: config.enableManagedSettingsinto bothsession.createandsession.resume.
That is why the same binary produces 257 deny events under the app and 0 when launched directly.
Impact
- Sessions silently lose all user MCP tooling. There is no
tools_changednotification, no banner, and
no error surfaced in the UI — only an[ERROR]line in a process log the user has no reason to read. - The message names a cause (
enterprise managed allow list) that does not exist on the account,
sending users to hunt for a nonexistent policy. At least one upstream reporter (#4392) read it as a
genuine corporate whitelist. - Agents and skills that depend on those servers fail in ways that look like the servers are
misconfigured, so users edit working configuration trying to fix it. - MCP child processes are spawned and then discarded, so the startup cost is paid for no benefit.
Affected Version
Reproduced on CLI payload 1.0.79-5, driven by desktop app 1.1.6 (commit=a9706b2); also observed
under app 1.1.5 (f459745). Windows 11 x64.
The mechanism is present in every payload from 1.0.72 onward. Marker counts taken directly from each
shipped app.js:
| CLI payload | managedAllowedMcpServerLists:[[]] |
host-wide fan-out to active sessions | managedSettingsResolutionInFlight |
|---|---|---|---|
| 1.0.71 | absent | 0 | 0 |
| 1.0.72 | present | 0 | 0 |
| 1.0.73 | present | 0 | 0 |
| 1.0.78-2 | present | 1 | 4 |
| 1.0.79-5 | present | 1 | 4 |
| 1.0.79-9 | present | 1 | 4 |
- 1.0.72 introduced the empty-allow-list interim state, behind a narrow session-local guard.
- 1.0.78-2 made it routine: it added fan-out to every active session and widened the trigger guard at
the same time (diff in Additional context). - 1.0.71 does not contain the mechanism at all and is the basis of the workaround.
Install timeline on this machine, from ~/.copilot/logs/github-app.*.log:
2026-08-04T07:42:36Z Detected Copilot CLI version at startup version=Copilot binary version: 1.0.78-2 source=Some(Bundled)
2026-08-07T15:03:37Z Detected Copilot CLI version at startup version=Copilot binary version: 1.0.79-5 source=Some(Bundled)
The desktop app version is a precondition, not the cause. copilot-sdk/index.js is byte-identical
between app 1.1.5 and 1.1.6 (SHA-256 648CD26BA299…, 449,504 bytes in both), and the app has passed
enableManagedSettings since 1.1.0 — so nothing changed on the app side across the window in which the
symptom appeared.
Note on reading versions:
copilot.exe --versionself-resolves to the newest installed payload and
will misreport which payload is actually running. Use--no-auto-update --version, or inspect the
loaded module path of a live process.
Affected version
CLI payload 1.0.79-5+. See the bottom of description for details on affected version
Steps to reproduce the behavior
⚠️ A hand-launched CLI will not reproduce this, and the clean log it produces is not evidence the
build is fine. Runningcopilot --server --stdio --no-auto-updatedirectly produced 0
[managedSettings]lines and 0 deny events, while the app-spawned same binary in the same hour
produced 823 and 257 respectively. The reason is in the scope note above: such a process is
never sentenableManagedSettings: true, so it never enters the managed-settings path at all — and
that holds even if you authenticate it. Reproduce through the desktop app.
- Install the desktop app (1.1.5 or 1.1.6) with a CLI payload of 1.0.78-2 or later.
- Sign in with an account that has no enterprise managed policy. Confirm with the process log — a
clean resolution on such an account looks like this:
2026-08-09T15:19:40.754Z [INFO] [managedSettings] self-fetch starting for account https://github.com/<user>
2026-08-09T15:19:40.755Z [INFO] [managedSettings] device MDM: no policy present on this device
2026-08-09T15:19:40.915Z [INFO] [managedSettings] server policy: none for this account (404/empty) from https://github.com
2026-08-09T15:19:40.916Z [INFO] [managedSettings] effective policy resolved: source=none, bypassDisabled=false, serverFetchFailed=false
2026-08-09T15:19:40.916Z [INFO] [managedSettings] applied: no bypass restriction in force (managed policy absent)
- Configure 2 or more user MCP servers in
~/.copilot/mcp-config.json. Transport does not matter —
stdio, http and local all fail together. Do not rely on the built-in GitHub server to show the
failure; it is exempt. - Open several app sessions in quick succession, or open a new session while another is still
starting. Opening a single session already issuessession.createand thensession.resumeroughly
2 s later, so one open produces two overlapping option updates; several opens make a collision
near-certain. - Search the CLI process logs for the denial:
Select-String -Path "$env:USERPROFILE\.copilot\logs\process-*.log" `
-SimpleMatch 'not permitted by enterprise managed allow list' |
Where-Object { $_.Line -match '^\d{4}-\d{2}-\d{2}T' -and $_.Line.Length -lt 300 }
The
^\d{4}-…anchor and the length filter are both necessary. Conversation transcripts and
fetched page text are written into these same logs, so an unanchored search matches quoted copies of
the message and overcounts badly. This produced false positives twice during the investigation.
Output — the denial, with the surrounding lifecycle. From
~/.copilot/logs/process-1786241789611-38652.log. Note the ordering and the millisecond deltas: the
skips land inside the fetch, and the fetch succeeds:
2026-08-09T02:16:45.131Z [INFO] [managedSettings] self-fetch starting for account https://github.com/<user>
2026-08-09T02:16:45.136Z [ERROR] Skipping MCP server "ado-skype": not permitted by enterprise managed allow list
2026-08-09T02:16:45.136Z [ERROR] Skipping MCP server "ado-domoreexp": not permitted by enterprise managed allow list
2026-08-09T02:16:45.136Z [ERROR] Skipping MCP server "mcp_windbg": not permitted by enterprise managed allow list
2026-08-09T02:16:45.136Z [ERROR] Skipping MCP server "obsidian-vault-coding-brain": not permitted by enterprise managed allow list
2026-08-09T02:16:45.196Z [INFO] [managedSettings] self-fetch complete for https://github.com/<user>: bypassDisabled=false, serverFetchFailed=false, serverCacheFallback=false, deviceLoadFailed=false
2026-08-09T02:16:45.634Z [WARNING] Failed to refresh persisted MCP tools for mcp_windbg; continuing with cached tools: Error: MCP server "mcp_windbg" is not connected
- deny window here = 65 ms; the servers were denied at +5 ms
serverFetchFailed=false— the policy fetch worked- the MCP child processes had already been spawned
(MCP server stderr {"server_name":"ado-domoreexp", ...}at02:16:45.079), so the work is done and
then thrown away
- To see the window itself and every trigger, pull the whole lifecycle:
Select-String -Path "$env:USERPROFILE\.copilot\logs\process-*.log" -SimpleMatch '[managedSettings]'
Output — a tighter variant, in which a server is denied 1 ms after the fetch already completed,
showing the interim policy is not cleared synchronously with completion:
2026-08-09T02:18:14.600Z [INFO] [managedSettings] self-fetch starting for account https://github.com/<user>
2026-08-09T02:18:14.605Z [ERROR] Skipping MCP server "ado-skype": not permitted by enterprise managed allow list
... (ado-domoreexp, mcp_windbg, obsidian-vault-coding-brain, same millisecond)
2026-08-09T02:18:14.619Z [INFO] [managedSettings] self-fetch complete for https://github.com/<user>: ... serverFetchFailed=false ...
2026-08-09T02:18:14.620Z [ERROR] Skipping MCP server "obsidian-vault-coding-brain": not permitted by enterprise managed allow list
Output — additional trigger sources, each of which opens its own deny window:
2026-08-09T02:18:18.068Z [INFO] [managedSettings] self-fetch starting for account device-only
2026-08-09T02:18:18.076Z [INFO] [managedSettings] self-fetch starting for account https://github.com/<user>
2026-08-09T02:18:18.079Z [INFO] [managedSettings] self-fetch result for device-only discarded: account changed while the fetch was in flight
Reproduction rate. Over the surviving log-retention window (2026-08-08T22:46Z – 2026-08-09T02:18Z),
during ordinary multi-session use: 42 skip records in 9 bursts across 2 CLI processes —
obsidian-vault-coding-brain ×15, ado-skype ×9, ado-domoreexp ×9, mcp_windbg ×9. The built-in
GitHub server was never skipped. Not every session is affected; which sessions lose servers permanently
is decided by the timing rule described in Additional context.
Expected behavior
On an account with no enterprise managed policy, all servers configured in ~/.copilot/mcp-config.json
should start and remain available for the life of the session. Concretely:
- No deny while the policy is merely undetermined. "We have not resolved the policy yet" should not
be encoded as "a policy exists and it permits nothing." Withsource=none,serverFetchFailed=false
andmanaged policy absent, zero servers should be filtered. - If a pre-emptive deny is genuinely required, it must be reversible. A server denied during the
interim window should be restored when the window closes, exactly as a server stopped by a real
policy tightening already is. Today the two cases behave differently and only one of them recovers. - Registration should wait for the policy rather than race it. The gate that exists for this purpose
should be in force during startup registration. - A resolution that has not failed should not disturb sessions that are already running. Servers
already connected should not be torn down for a routine re-resolution in another session. - If servers are withheld, say so where the user will see it. The failure is currently silent in the
UI, and the message that does exist blames an enterprise policy that is not present on the account.
Additional context
Where the code is
All references are to the shipped bundle:
%LOCALAPPDATA%\copilot\pkg\win32-x64\1.0.79-5\app.js (9,317,686 chars, 6,135 lines)
The file is bundled one module per line, so the relevant lines are 100–150 KB wide (line 2345 is 147,313
chars; line 2398 is 114,363). A line number alone will not locate anything — use line:col, or the byte
offset. Both are given; either can be re-derived by searching for the symbol name, which is unminified.
| # | What | Symbol | line:col | byte offset |
|---|---|---|---|---|
| 1 | Producer emits the empty allow list | composedManagedMcpHostOptions |
2398:44941 | 2147203 |
| 1a | The literal itself | managedAllowedMcpServerLists:[[]] |
2398:45016 | 2147278 |
| 2 | Consumer's length > 0 test |
managedMcpPolicyPlan |
2345:75295 | 1893804 |
| 3 | Tighten + relax, both halves | enforceManagedMcpPolicy |
2345:76770 | 1895279 |
| 3a | The only .set() in the bundle |
policyRemovedConfigs.set( |
2345:77656 | 1896165 |
| 4 | Deny path A — bulk start loop | after managedMcpPolicyBlockReasonForBulk |
2345:79639 | 1898148 |
| 5 | Deny path B — first check | startServerOnce |
2345:93949 | 1912458 |
| 6 | Deny path C — second check | startServerOnce |
2345:94664 | 1913173 |
| 7 | The gate that opts itself out | expectManagedMcpPolicy |
2398:43337 | 2145599 |
| 8 | Host-wide fan-out | beginManagedSettingsResolution |
2398:42842 | 2145104 |
| 9 | The trigger | updateOptions |
2402:67610 | 2284349 |
| 10 | Client-supplied flag default | enableManagedSettings ?? !1 |
2402:40113 | 2256852 |
To re-derive line:col from an offset ([regex]::Matches over a 9 MB string hangs — use IndexOf):
$s = [IO.File]::ReadAllText("$env:LOCALAPPDATA\copilot\pkg\win32-x64\1.0.79-5\app.js")
$off = $s.IndexOf('policyRemovedConfigs.set(', [StringComparison]::Ordinal)
$line = 1; $i = $s.IndexOf("`n"); $last = 0
while ($i -ge 0 -and $i -lt $off) { $line++; $last = $i + 1; $i = $s.IndexOf("`n", $i + 1) }
"line $line col $($off - $last + 1) offset $off"
There is a fourth mcpHostStateAddFiltered(...) call at 2345:93083 / offset 1911592, inside
applyConfigFilterToServer. It is not part of this defect — it is the separate config-filter path
and logs MCP server "X" filtered: <reason>, not the allow-list message. Noted so the count is not
mistaken for four managed-policy deny sites; there are three.
Root cause
1. The producer emits "a policy exists and it allows nothing"
composedManagedMcpHostOptions() {
if (this.managedMcpInterimFailClosed)
return { managedAllowedMcpServerLists: [[]] }; // <-- one list, containing nothing
let e = this.composeManagedMcpPolicy();
return {
managedAllowedMcpServerLists: e.allowedLists.length > 0 ? e.allowedLists : void 0,
managedDeniedMcpServers: e.denied.length > 0 ? e.denied : void 0
};
}
Note the asymmetry with the normal path directly below it: when there is genuinely no policy the normal
path returns void 0, but the interim path returns [[]].
2. The consumer's "is a policy configured?" test cannot tell [[]] from a real policy
async managedMcpPolicyPlan(e, n) {
let r = this.managedAllowedMcpServerLists ?? [],
o = r.length > 0, // [[]].length === 1 -> true
s = (this.managedDeniedMcpServers?.length ?? 0) > 0;
return !o && !s
? { removeNames: [], logMessages: [] }
: await h.mcpHostManagedMcpPolicyActions(/* ... */);
}
[[]] is truthy for length > 0, so the native policy engine runs against an empty allow list and
removes everything not exempt. The user-visible string comes from o.logMessages[0], produced by the
native addon — which is why grepping app.js for not permitted by enterprise managed allow list finds
nothing.
Exemption set — why built-ins survive and every user server dies together regardless of transport:
isRegisteredServerTrustedFirstParty(e) {
return this.trustedDynamicStartServerNames.has(e)
|| this.trustedDefaultServerNames.includes(e)
|| this.trustedExternalServerNames.has(e);
}
// trustedDefaultServerNames keeps only entries whose source === "builtin"
3. The bug's teeth: the startup deny paths are not restorable
enforceManagedMcpPolicy contains both halves of the policy transition. On tighten it stops the server
and stashes its config; on relax it replays only what was stashed:
// tighten: stop + stash
try { await this.stopServer(l); } catch (d) { /* ... */ }
this.policyRemovedConfigs.set(l, c); // <-- the ONLY .set() in the file (2345:77656)
delete this.config.mcpServers[l];
// relax: restore
for (let [l, c] of [...this.policyRemovedConfigs])
if (!s.has(l)) {
if (this.config.mcpServers[l]) { this.policyRemovedConfigs.delete(l); continue; }
try { await this.startServer(l, c); } catch (d) { /* ... */ continue; }
this.config.mcpServers[l] && (this.policyRemovedConfigs.delete(l), a = !0);
}
But a server denied while starting never reaches that stash. startServerOnce checks the policy
twice and both times just marks the server filtered and returns:
async startServerOnce(e, n, r, o, s = !1) {
let a = this.currentServerStopVersion(e);
if (!this.validateServerConfig(e, n)) return;
let l = await this.managedMcpPolicyBlockReason(e, n, s);
if (l) { h.mcpHostStateAddFiltered(this.hostStateHandle, e); this.logger.log(l); return; } // 2345:93949
/* ... gate plan, config filtering ... */
let g = await this.managedMcpPolicyBlockReason(e, d, s);
if (g || this.currentServerStopVersion(e) !== a) {
g && (h.mcpHostStateAddFiltered(this.hostStateHandle, e), this.logger.log(g)); return; // 2345:94664
}
this.config.mcpServers[e] = d; // only reached when NOT blocked
/* ... connect ... */
}
and the bulk path additionally deletes the config outright:
let d = await this.managedMcpPolicyBlockReasonForBulk(l, c);
if (d) {
this.logger.log(d);
h.mcpHostStateAddFiltered(this.hostStateHandle, l);
delete this.config.mcpServers[l]; // <-- dropped, never stashed for restore (2345:79639)
return;
}
There is exactly one policyRemovedConfigs.set(...) in the bundle, and none of the three
managed-policy deny paths is it. Therefore:
| when the window opens | outcome |
|---|---|
| server already registered | stopped, stashed, restored automatically when the policy clears |
| server registering right now | filtered + config dropped, never restored — dead for the life of the session |
A fresh session registers its MCP servers at session start, which is exactly when the window opens, so
the common case is the non-restorable one. This single asymmetry is why the failure looks
non-deterministic, and why "it fixed itself after a minute" and "it was dead for the whole session" are
both accurate reports of the same defect.
Servers in the second row can still come back if a later full MCP client rebuild happens to re-register
them from the original config — the teardown/rebuild described in #4392. That is incidental rather than
the restore path, and it can be denied again: I measured 4 consecutive bursts on the same 4 servers
within 90 s.
4. What opens the window, and why it opens so often
beginManagedSettingsResolution() {
if (this.lastManagedSettingsLayers) {
this.managedSettingsResolutionInFlight = !0;
this.managedSettingsPolicyGeneration++;
for (let n of this.activeSessions.values())
n.beginManagedSettingsResolution({ permissions: !1 }); // fan-out to EVERY live session
}
}
{permissions:false} skips the permissions half, but the session-level handler still always calls
beginInterimManagedMcpFailClosed(). A session that joins while a resolution is in flight is
immediately fail-closed too:
applyCurrentManagedSettingsPolicy(e, n, r) {
/* ... */ this.managedSettingsResolutionInFlight
? e.beginManagedSettingsResolution({ permissions: !1 })
: /* ... */
}
There is a gate intended to make MCP registration wait for the policy, but it opts itself out whenever
the host already exists, so registration proceeds against the interim [[]]:
expectManagedMcpPolicy() {
if (this.managedMcpPolicyApplied || this.mcpHost || this.managedMcpPolicyGate) return;
/* ...create gate... */
}
// consumer:
this.managedMcpPolicyGate && await this.managedMcpPolicyGate.promise; // no gate -> awaits nothing
The 1.0.78-2 trigger-guard change
// 1.0.73 — narrow: authInfo present AND identity defined AND identity changed; session-local effect
if (e.authInfo !== void 0 && this.enableManagedSettings) {
let l = YX(this.authInfo);
l !== void 0 && l !== this.managedSettingsFetchIdentity && (
this.managedSettingsFetchIdentity = l,
this.beginInterimManagedMcpFailClosed(), // this session only
/* ... */);
}
// 1.0.78-2 and later — broad: key merely present, undefined identity no longer suppresses,
// plus a generation check; effect is now host-wide
if (this.enableManagedSettings && ("authInfo" in e || e.enableManagedSettings === !0)) {
let c = ene(this.authInfo);
(c !== this.managedSettingsFetchIdentity ||
this.managedSettingsAuthGeneration !== this.managedSettingsFetchGeneration) && (
this.managedSettingsFetchIdentity = c,
this.managedSettingsFetchGeneration = this.managedSettingsAuthGeneration,
this.beginManagedSettingsResolution(), // fan-out to ALL sessions
/* ... */);
}
Three widenings at once: e.authInfo !== void 0 became "authInfo" in e; the l !== void 0 guard was
dropped; and a generation disjunct was added. managedSettingsAuthGeneration++ (new in 1.0.78-2, fired
on auth_info_updated) means each auth refresh arms the next options update to re-trigger the cycle.
The hourly refreshManagedSettings() timer does not call beginManagedSettingsResolution() — only
updateOptions does. So the window is tied to session churn, not to the timer.
Suggested fixes (any one breaks the chain)
- Do not use
[[]]as a sentinel. Represent "policy undetermined" as a distinct flag, or make the
consumer testr.some(list => list.length > 0)instead ofr.length > 0. - Make the deny paths restorable — have the
startServerOnceand bulk deny paths record the config
intopolicyRemovedConfigs(or an equivalent) so the "policy relaxed" loop can replay them. This is
the smallest change that removes the permanent-loss case. - Fix the gate — drop
|| this.mcpHostfromexpectManagedMcpPolicy()so registration waits for
the resolution instead of racing it. - Do not fan out to already-running sessions, or at minimum do not tear down servers that are
already connected for a resolution that has not failed.
Workaround (verified)
Pin the CLI to 1.0.71, which does not contain the mechanism at all:
[Environment]::SetEnvironmentVariable(
'COPILOT_CLI_PATH',
"$env:LOCALAPPDATA\github-copilot-sdk\cli\1.0.71\copilot.exe",
'User')
# then fully exit and restart the app
Confirmed applied — note that the reported version string is unreliable here; the path is ground truth:
2026-08-09T05:01:55Z Detected Copilot CLI version at startup version=GitHub Copilot CLI 1.0.79-9
source=Some(Local) path="...\github-copilot-sdk\cli\1.0.71\copilot.exe"
bundled_version="1.0.79-5"
All 8 running copilot.exe processes load their payload from pkg\win32-x64\1.0.71\, and there have
been zero deny events in the ~11 h since, across continued multi-session use.
What we could not determine
- Whether the interim state is ever legitimately needed. If the intent is to fail closed only when
the policy genuinely cannot be determined, then theserverFetchFailed=false/managed policy absent
case observed here should never have entered it at all. - Exact first occurrence.
process-*.logfiles rotate; the oldest surviving one starts
2026-08-08T22:24Z. Earlier deny events were observed during this investigation but those files are now
gone. The version matrix is the reliable dating, not the logs. - Why the perceived onset was 2026-08-08 when 1.0.78-2 shipped on 08-04. Best explanation: the
fan-out only bites when sessions overlap, and concurrent multi-session usage began on 08-08. Not
proven — offered as a hypothesis, not a finding.
Related issues
- #4378 — "…registry policy fetch fails with 401/403 on GHEC data residency, silently blocking all
user-configured MCP servers" — same symptom, different cause (fetch fails). Here the fetch
succeeds and there is no policy, so this is a distinct defect that #4378's fix would not cover. - #4349 — "Managed settings policy fetch fails closed on valid enum value…" — another fail-closed
input-handling variant. Also fetch-side. - #4392 — post-auth MCP client teardown/rebuild. Relevant twice over: it is the incidental mechanism
behind the observed silent recoveries, and its author also read the allow-list message as a real
corporate policy.
Please do not close this as a duplicate of the fetch-failure family. The discriminator is one field
in the resolved line: every filed variant has serverFetchFailed=true; this one has
serverFetchFailed=false with source=none / managed policy absent. Relatedly, the v1.0.78 release
note advertises a fail-closed → fail-open change, but that covers the fetch-failure path only. This
variant is untouched by it and in fact reproduces on 1.0.79-5, which postdates it.
A third-party report quoting Skipping MCP server "umacs-build-bridge" dated 2026-08-03 matches the
1.0.72/1.0.73 era, consistent with the mechanism landing in 1.0.72.
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu bằng cách lần theo việc xử lý các cài đặt được quản lý trong app.js được cung cấp, đặc biệt là enableManagedSettings, managedAllowedMcpServerLists và các đường dẫn khôi phục; so sánh cách nối payload của client trong copilot-sdk/index.js. Sử dụng nhật ký tiến trình và việc tái hiện với ~/.copilot/mcp-config.json để xác minh vòng đời. Hoàn tất khi các máy chủ MCP của người dùng vẫn khả dụng khi quá trình phân giải chính sách báo cáo source=none, với phạm vi bao phủ phù hợp cho khoảng thời gian chuyển tiếp và hành vi khôi phục.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript
- Lĩnh vực
- cli
- Loại issue
- Lỗi
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 45/100