# How SecureDesign Manages and Persists Workspace State Across VS Code Sessions

> **SecureDesign uses a singleton `WorkspaceStateService` that wraps VS Code's `ExtensionContext.workspaceState` Memento API to generate stable workspace identifiers and namespace all stored keys, ensuring chat history, secrets, ...

- Repository: [Harold Martin/secure-design](https://github.com/hbmartin/secure-design)
- Tags: internals
- Published: 2026-03-03

---

**SecureDesign uses a singleton `WorkspaceStateService` that wraps VS Code's `ExtensionContext.workspaceState` Memento API to generate stable workspace identifiers and namespace all stored keys, ensuring chat history, secrets, and UI state survive editor restarts while remaining isolated between projects.**

The SecureDesign extension provides a robust mechanism to manage and persist workspace state across different VS Code sessions, solving the common challenge of maintaining context when the editor closes or switches between projects. By leveraging VS Code's built-in persistence layer through a carefully architected abstraction, SecureDesign ensures that sensitive data like API keys and conversation history remain available yet properly sandboxed per workspace.

## Understanding VS Code's Workspace State API

VS Code extensions receive an `ExtensionContext` during activation that exposes `workspaceState`—a `Memento` object providing `get` and `update` methods for persistent key-value storage. According to the SecureDesign source code, this API automatically handles disk serialization, but lacks built-in workspace isolation and stable identifiers for multi-root scenarios.

## The WorkspaceStateService Singleton Pattern

SecureDesign centralizes all persistence logic in [`src/services/workspaceStateService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/workspaceStateService.ts), implementing a singleton pattern to ensure consistent state management throughout the extension lifecycle.

### Service Initialization

The extension's entry point in [`src/extension.ts`](https://github.com/hbmartin/secure-design/blob/main/src/extension.ts) instantiates the service during activation and injects the VS Code context:

```typescript
const workspaceStateService = WorkspaceStateService.getInstance();
workspaceStateService.initialize(context);

```

This initialization sequence (lines 34-38 in [`src/extension.ts`](https://github.com/hbmartin/secure-design/blob/main/src/extension.ts)) passes the `ExtensionContext` to the service, enabling subsequent calls to `context.workspaceState` for actual persistence operations.

### Generating Stable Workspace Identifiers

To manage and persist workspace state reliably across sessions, SecureDesign generates deterministic workspace IDs that remain constant for a given project even in multi-root configurations. The `getWorkspaceId()` method in [`src/services/workspaceStateService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/workspaceStateService.ts) (lines 40-59) concatenates sorted folder URIs and hashes the result:

```typescript
public getWorkspaceId(): string {
  const folders = vscode.workspace.workspaceFolders || [];
  const sortedPaths = folders
    .map(f => f.uri.toString())
    .sort();
  
  // Create hash for stable, short identifier
  const hash = crypto.createHash('sha256')
    .update(sortedPaths.join('::'))
    .digest('hex')
    .slice(0, 16);
    
  return hash;
}

```

This approach ensures that the same physical workspace always produces the same identifier, while different workspaces produce unique hashes, preventing data collision.

## Namespaced Key Management for Data Isolation

SecureDesign prevents cross-workspace data leakage by namespace-prefixing all storage keys. When persisting values, the service constructs keys in the format `prefix::hashedWorkspaceId` (lines 78-88 in [`src/services/workspaceStateService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/workspaceStateService.ts)):

```typescript
private buildKey(key: string): string {
  const workspaceId = this.getWorkspaceId();
  return `${key}::${workspaceId}`;
}

public async update<T>(key: string, value: T): Promise<void> {
  const namespacedKey = this.buildKey(key);
  await this.context.workspaceState.update(namespacedKey, value);
}

```

This namespacing strategy ensures that `securedesign.chatHistory` stored in Workspace A cannot be accessed from Workspace B, as the full storage key includes the workspace-specific hash.

## Detecting Workspace Changes

To manage state transitions when users switch between projects, SecureDesign implements change detection via `hasWorkspaceChanged()` (lines 64-67 in [`src/services/workspaceStateService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/workspaceStateService.ts)). The extension subscribes to VS Code's `onDidChangeWorkspaceFolders` event in [`src/extension.ts`](https://github.com/hbmartin/secure-design/blob/main/src/extension.ts) (lines 73-88) and uses this method to invalidate stale data:

```typescript
let previousId = workspaceStateService.getWorkspaceId();

vscode.workspace.onDidChangeWorkspaceFolders(() => {
  const currentId = workspaceStateService.getWorkspaceId();
  if (workspaceStateService.hasWorkspaceChanged(previousId)) {
    // Clear per-workspace data, e.g. chat history
    await chatRepo.clearChatHistory();
    previousId = currentId;
  }
});

```

This mechanism ensures that chat history and UI state from a previous workspace do not persist into a newly opened project.

## Practical Usage Examples

SecureDesign's persistence layer supports multiple extension components through dependency injection and direct service usage.

### Persisting Chat History

The `ChatMessagesRepository` in [`src/chat/ChatMessagesRepository.ts`](https://github.com/hbmartin/secure-design/blob/main/src/chat/ChatMessagesRepository.ts) (lines 6-20) utilizes the workspace service to maintain conversation history across sessions:

```typescript
export class ChatMessagesRepository {
  private readonly storageKey = 'securedesign.chatHistory';
  
  constructor(private workspace: WorkspaceStateService) {}
  
  async saveHistory(messages: ChatMessage[]): Promise<void> {
    await this.workspace.update(this.storageKey, messages);
  }
  
  getHistory(): ChatMessage[] {
    return this.workspace.get<ChatMessage[]>(this.storageKey) || [];
  }
}

```

This implementation ensures that chat context survives editor restarts while remaining isolated to the specific workspace.

### Storing Secrets Securely

For sensitive data like API keys, SecureDesign exposes VS Code's secret storage through the same service interface. The `secrets()` method in [`src/services/workspaceStateService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/workspaceStateService.ts) (lines 20-22) forwards to `ExtensionContext.secrets`, providing encrypted storage:

```typescript
public secrets(): vscode.SecretStorage {
  return this.context.secrets;
}

```

Components access this via the singleton to store and retrieve credentials without exposing them in plain text workspace state.

## Summary

SecureDesign manages and persists workspace state across VS Code sessions through a layered architecture that combines VS Code's native APIs with custom isolation mechanisms:

- **Singleton Pattern**: `WorkspaceStateService` centralizes all persistence logic and maintains a single instance throughout the extension lifecycle.
- **Stable Identifiers**: Deterministic workspace IDs generated from sorted folder URIs ensure consistent state retrieval across sessions.
- **Namespace Isolation**: All storage keys include the workspace hash, preventing data leakage between projects.
- **Change Detection**: Automatic invalidation of stale data when users switch workspaces via `onDidChangeWorkspaceFolders` event handling.
- **Secure Storage**: Delegation to VS Code's encrypted `SecretStorage` for API keys and credentials.

## Frequently Asked Questions

### How does SecureDesign isolate data between different workspaces?

SecureDesign isolates data by namespace-prefixing every storage key with a unique workspace identifier. The `WorkspaceStateService` generates a SHA-256 hash from the sorted list of workspace folder URIs, then appends this hash to storage keys (e.g., `securedesign.chatHistory::a1b2c3d4`). This ensures that Workspace A and Workspace B cannot access each other's persisted data, even when running simultaneously in separate VS Code windows.

### What happens to workspace state when I switch projects?

When you open a different workspace folder, SecureDesign detects the change through the `onDidChangeWorkspaceFolders` event. The extension compares the previous workspace ID with the current one using `hasWorkspaceChanged()`. If they differ, the extension clears workspace-specific data such as chat history to prevent context contamination from the previous project. This ensures that each project starts with its own persisted state or a clean slate if no prior state exists.

### Can I access workspace state outside of the main extension activation?

Yes, but only through the `WorkspaceStateService` singleton. Since the service is initialized during the `activate` function in [`src/extension.ts`](https://github.com/hbmartin/secure-design/blob/main/src/extension.ts) with the VS Code extension context, any component that imports the service can access persisted state. The service uses a singleton pattern (`WorkspaceStateService.getInstance()`), meaning the same instance—and therefore the same initialized context—is available throughout the extension lifecycle, including in command handlers, tree providers, and chat repositories.

### How does SecureDesign handle multi-root workspace scenarios?

SecureDesign handles multi-root workspaces by incorporating all folder URIs into the workspace identifier calculation. The `getWorkspaceId()` method sorts the array of workspace folder URIs alphabetically before hashing them, ensuring that the same set of folders produces the same identifier regardless of order. This means that if you have folders `A` and `B` open, the ID will be consistent whether you opened `A` then `B` or `B` then `A`. If you later add folder `C`, the workspace ID changes, triggering the change detection mechanism to reset project-specific state.