github / github/copilot-sdk

SessionMetadataSnapshot.AlreadyInUse is always false for SDK-resumed sessions (never reflects concurrent use by another process)

オープン
#1,749 コメント 1 件 リアクション 1 件 担当者 0 名 GitHub で見る
bug
主要言語
Java
スター
10.5k
フォーク
1.5k
平均マージ
1日 11時間
マージ済み PR(30日)
128

説明

## Summary

`session.Rpc.Metadata.SnapshotAsync()` returns a `SessionMetadataSnapshot` whose `AlreadyInUse` is **always `false`** when a session is opened through the SDK — even when the same session is concurrently held open by a different live process. The documented contract says this field should be `true` "when the session was detected to be in use by another process."

The in-use information clearly exists at the time of the call (a live per-process lock file is present on disk in the session directory), but it is not reflected in the snapshot. This affects both a default resume and a resume with `SuppressResumeEvent = true`.

## Environment

- SDK: `GitHub.Copilot.SDK` (.NET) **v1.0.1**
- Bundled Copilot CLI: **1.0.63** (managed automatically by the SDK)
- OS: Windows
- Note: the root cause appears to be server-side (Copilot CLI), since the .NET client deserializes `alreadyInUse` straight from the `session.metadata.snapshot` RPC response. The same field exists in every language SDK (`alreadyInUse` / `AlreadyInUse`), so all clients are likely affected.

## Expected behavior

Per the generated contract for `SessionMetadataSnapshot.AlreadyInUse`:

> True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions.

So when a session is resumed while another live process is holding it, `AlreadyInUse` should be `true`.

## Actual behavior

`AlreadyInUse` is `false` in that scenario.

## Minimal reproduction

Self-contained: two `CopilotClient` instances in one program (each spawns its own CLI server process, so the second one is genuinely "another process" relative to the first).

```csharp
using GitHub.Copilot;

#pragma warning disable GHCP001 // SnapshotAsync is experimental

// Client A creates a session, sends one message so it persists to disk,
// then stays alive (its CLI server keeps holding the session).
await using var holder = new CopilotClient();
var held = await holder.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
});
var sessionId = held.SessionId;
await held.SendAndWaitAsync(new MessageOptions { Prompt = "Reply with exactly: OK" });

// Client B (independent client + CLI server) resumes the SAME session
// using the DEFAULT resume path, then reads the snapshot.
await using var probe = new CopilotClient();
await using var resumed = await probe.ResumeSessionAsync(sessionId, new ResumeSessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
});

var snapshot = await resumed.Rpc.Metadata.SnapshotAsync();
Console.WriteLine($"AlreadyInUse = {snapshot.AlreadyInUse}"); // prints False; expected True
```

### Observed output

```
[holder] Created session: f5fdd147-8928-4759-b74c-fd3ffc2b469b
[disk] events.jsonl persisted: True (27817 bytes)
[disk] Lock files present (holder still alive): inuse.20772.lock
[probe] snapshot.AlreadyInUse = False (EXPECTED: True — held by [holder] process)
```

While `probe` calls `SnapshotAsync()`, the session directory
`~/.copilot/session-state/f5fdd147-.../` contains `inuse.20772.lock`, and PID `20772`
(the holder's CLI server) is alive — i.e. the session is demonstrably in use by another
process, yet the snapshot reports `AlreadyInUse = False`.

## Supporting evidence

- The session directory contains a per-process lock file named `inuse..lock` whose contents are the owning process PID. During the repro this file exists and its PID is a live process, so the "in use by another process" condition is objectively true at snapshot time.
- The behavior is identical with `SuppressResumeEvent = true` and with the default resume, so it is not a side effect of suppressing the resume event.

## Hypothesized root cause

On the SDK resume path, the runtime does not surface the in-use/lock-detection result onto the metadata snapshot — `AlreadyInUse` is left at its default (`false`). The underlying data is available (the lock files are on disk and detection runs), so this looks like the computed value simply isn't propagated to the snapshot for SDK-initiated resumes, whereas a brand-new session correctly reports `false`.

## Test coverage gap / suggested fix

The existing e2e tests assert `AlreadyInUse == false` only for a freshly-created session, which is correct per the "Always false for new sessions" clause, but none cover a session that is concurrently in use:

- `dotnet/test/E2E/RpcSessionStateE2ETests.cs` → `Assert.False(initialSnapshot.AlreadyInUse);`
- `go/internal/e2e/rpc_session_state_e2e_test.go`
- `nodejs/test/e2e/rpc_session_state.e2e.test.ts` → `expect(initialSnapshot.alreadyInUse).toBe(false);`

Suggested additions:
1. Populate `alreadyInUse` on the snapshot from the same in-use detection used elsewhere, on the SDK resume path.
2. Add an e2e test: create + hold a session in one client, resume it from a second client, and assert the second client's snapshot reports `AlreadyInUse == true`.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Start with the resume and snapshot behavior described in dotnet/test/E2E/RpcSessionStateE2ETests.cs, then compare the corresponding Go and Node.js end-to-end tests. Trace SDK resume handling and the session.metadata.snapshot response to find where the detected lock state is lost. Done means a held session resumed by another client reports AlreadyInUse=true, while new sessions remain false.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
csharp, go, typescript
領域
api, backend, testing
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。