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, 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, implementing a singleton pattern to ensure consistent state management throughout the extension lifecycle.
Service Initialization
The extension's entry point in src/extension.ts instantiates the service during activation and injects the VS Code context:
const workspaceStateService = WorkspaceStateService.getInstance();
workspaceStateService.initialize(context);
This initialization sequence (lines 34-38 in 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 (lines 40-59) concatenates sorted folder URIs and hashes the result:
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):
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). The extension subscribes to VS Code's onDidChangeWorkspaceFolders event in src/extension.ts (lines 73-88) and uses this method to invalidate stale data:
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 (lines 6-20) utilizes the workspace service to maintain conversation history across sessions:
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 (lines 20-22) forwards to ExtensionContext.secrets, providing encrypted storage:
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:
WorkspaceStateServicecentralizes 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
onDidChangeWorkspaceFoldersevent handling. - Secure Storage: Delegation to VS Code's encrypted
SecretStoragefor 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →