github / github/copilot-sdk

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

Ouverte
#1,749 1 commentaire 1 réaction 0 personnes assignées Voir sur GitHub
bug
Langage dominant
Java
Étoiles
10.5k
Forks
1.5k
Merge moyen
1 j 11 h
PR mergées (30 j)
128

Description

## 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`.

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

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.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
csharp, go, typescript
Domaine
api, backend, testing
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Calme
Clarté
Plutôt claire
Accessibilité débutants
48/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.