Skip to content

feat(appstash): shared stash identity, session fields, secret codec, atomic 0600 writes - #105

Merged
pyramation merged 1 commit into
mainfrom
feat/shared-config-store
Aug 4, 2026
Merged

feat(appstash): shared stash identity, session fields, secret codec, atomic 0600 writes#105
pyramation merged 1 commit into
mainfrom
feat/shared-config-store

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Step 1 of consolidating our four credential stores onto one (constructive-planning#1430). createConfigStore is already the canonical store — the generated csdk executors use it — so this makes it capable enough that the csdk local duplicate, the agent CLI's account.json/backend-config.json, and the desktop AccountStore can all become callers rather than reimplementations.

One signed-in state across several binaries. Generated SDK CLIs pass their own CLI name as the tool name, which today means each one gets its own hidden dir and its own login. New stashName separates where state lives from what the tool is called:

createConfigStore('csdk',    { stashName: 'constructive' });
createConfigStore('agent',   { stashName: 'constructive' });  // same contexts + credentials

toolName still drives the CSDK_TOKEN / CSDK_API_ENDPOINT env prefixes and the command names in getClientConfig's error text, so a shared dir doesn't blur the tools' identities. Omitting stashName keeps today's per-tool isolation.

Session identity on credentials. ContextCredentials gains userId, email, apiKey, keyId, apiKeyExpiresAt, signedInAt — the fields StoredSession carries in the agent CLI and desktop today. Additive and optional.

SecretCodec for at-rest encryption. Secret-bearing fields (token, refreshToken, apiKey) pass through an optional codec on the way to disk; everything else stays readable for debugging. Desktop supplies an Electron safeStorage codec and gets the same file layout as the CLI:

codec: { name: 'electron-safeStorage', encode: , decode:  }

The codec name is recorded in credentials.json, so credentials written by a different codec are reported (sign in again to rewrite them) instead of being decoded into garbage.

Durability and permissions. writeJson is now atomic (temp file + rename, mode applied before the rename) and defaults to 0600 for every file, not just credentials.json — context files and vars can hold endpoints and secrets too.

No silent swallowing. readJson previously returned the caller's fallback when a file existed but didn't parse, so a half-written or hand-edited file looked like "no config" and got overwritten; listContexts did the same per file. Both now throw Malformed JSON in <path>. Missing files still return the fallback — absence is normal, corruption isn't.

Tests: __tests__/config-store-shared.test.ts (12 cases — shared/isolated stash, env-prefix independence, identity round-trip, codec encode/decode + mismatch + empty store, 0600 on all files, no temp leftovers, throw-on-malformed). Existing 77 appstash tests unchanged and passing; consumers (confstash, genomic, create-gen-app-test) pass.

Link to Devin session: https://app.devin.ai/sessions/783ae9ff63b445879450d5051d9d3d83
Requested by: @pyramation

@pyramation pyramation self-assigned this Aug 4, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 44e4a29 into main Aug 4, 2026
55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant