All writing
Copilot SDKAzureTypeScriptAI Tools

I Had a Microsoft Interview in Two Weeks. So I Built a Resume Tool on Their SDK.

March 4, 2026

Multiple friends had asked me to help them tailor resumes. The process is always the same: read the job posting, pull out keywords, rewrite bullet points to match, reorder sections by relevance, check for gaps. It takes 30-60 minutes per application. When you're applying to 20+ roles, that math doesn't work.

I already had a tool for this — ApplyAI on my ITeachYouAI site. It used Claude directly, no auth, no persistence. You'd paste a resume and a job posting, get suggestions back. It worked, but it was disposable. No saved history, no iterative refinement, no downloadable output.

Then I got a call about a Senior Partner Engineer role at GitHub. Interview in two weeks.

Why I chose Microsoft's entire stack

I could have rebuilt ApplyAI with better features on Supabase and Vercel. That's what I know. But sitting in an interview at GitHub — which is Microsoft — saying "I used Vercel" doesn't signal anything useful.

My exact thinking at the time: "I'm trying to think like Microsoft wants. What would Microsoft genuinely be impressed with?"

The answer was obvious. Use their stuff. All of it.

LayerChoiceWhy
AIGitHub Copilot SDKBrand new (released Jan 14, 2026). Show I can ship on a moving target
DatabaseAzure Cosmos DBMicrosoft's flagship NoSQL. Partition-key isolation per user
AuthAzure Entra IDEnterprise identity, not hobby auth
HostingAzure Container AppsOfficial deployment target for the SDK
SecretsAzure Key Vault + managed identity"I used Key Vault" vs "I used .env files" is the difference between enterprise thinking and hobby project thinking

The Copilot SDK was 5 weeks old when I started. Technical Preview. Known breaking changes. The CLI had silently updated its protocol flags once already, breaking every SDK version before 0.1.23. I pinned versions and kept going.

The interview narrative I wanted: "I built a real app on a brand-new SDK that's 5 weeks old, navigated the rough edges, dealt with breaking changes, and shipped anyway." That's more useful to an interviewer than another Next.js CRUD app.

The first thing I got wrong

I scaffolded the monorepo, got Express streaming through the Copilot SDK, built a React frontend rendering tokens in real-time. Phase 1 worked. The AI said "Ready for Phase 2?"

Then I stopped myself. "Did we plan all this stuff? Are we following a PRD? What's our planning document? What's our planning process?"

The honest answer to every one of those questions was: no.

I'd jumped straight into building because the SDK was exciting and the deadline was real. But I had no product requirements, no wireframes, no task breakdown. I was two days from having to explain this project in an interview and I couldn't articulate what it actually did beyond "resume stuff with AI."

Wrote the PRD. Wrote wireframes. Broke everything into 153 tasks across 7 phases. Then kept building.

GitHub doesn't support OpenID Connect

The auth plan was GitHub first — it's the most natural provider for a Copilot SDK project. Then Google. Then email/password.

Research killed that plan in 20 minutes.

Azure Entra External ID doesn't support GitHub as a social identity provider. GitHub doesn't implement standard OpenID Connect. There's no /.well-known/openid-configuration endpoint. Entra needs OIDC-compliant providers to federate identity.

GitHub OAuth works fine standalone, but it can't plug into Entra's identity framework. For a project designed to showcase Azure fluency, bolting on a separate OAuth implementation defeats the purpose.

Pivoted to Microsoft accounts first. Deferred Google and email/password to a later phase. One integration instead of three separate OAuth implementations.

The second surprise was the auth architecture. I'd planned server-side OAuth flows — redirect to provider, exchange code, set session cookie. Research showed the modern pattern: MSAL handles auth entirely in the browser. The backend only validates JWTs. That eliminated three backend endpoints and simplified the whole thing.

Nine bugs, one revert

Phases 4 and 5 — persistence and document export — went fast. Too fast. I merged the branch and started testing manually.

Self-review found nine bugs before I shipped:

  1. Race condition — auto-save useEffect fired multiple POSTs during streaming
  2. XSSdangerouslySetInnerHTML with unsanitized markdown output
  3. Puppeteer rendering unsanitized HTML — script injection risk
  4. DOCX renderer silently dropped content with brittle HTML regex
  5. @types/marked v5 conflicts with marked v17 built-in types
  6. upsertUser overwrote createdAt on every login
  7. No Puppeteer concurrency limit — headless Chromium in parallel with no queue
  8. No runtime validation on tool_result SSE event shape
  9. @types/marked in devDependencies — wrong location

I reverted the entire branch. Rebuilt with DOMPurify for sanitization, marked.lexer() AST instead of regex for DOCX generation, and proper concurrency handling. The second attempt shipped clean.

The tool bugs nobody tested

After deployment, I clicked through the app as a user. Upload resume. Paste job posting. Chat with the AI. Ask it to tailor.

Four bugs. All in the core feature — the thing the entire app exists to do.

The AI lost context between messages. save_document reported success without actually saving. get_documents returned data the frontend couldn't use. The tailored resume wasn't available in follow-up conversation turns.

All four shipped to production. All four were found by hand. There were zero integration tests on the AI pipeline.

My reaction at the time: "How come we never automated tested any of this shit?"

The answer was uncomfortable. Unit tests existed for config, for the tool registry, for auth middleware. But the actual chat-with-AI-and-get-a-resume flow — the thing a user would do — had no tests. 279 tests total, zero on the core path.

30 security issues post-deployment

After the tool bugs were fixed, I ran a security audit. Every route handler, every test file, every config.

30 issues. 1 critical, 10 high, 12 medium, 7 low.

The critical one: E2E_SKIP_AUTH in the auth middleware had no production guard. If someone set that environment variable in production, all authentication was bypassed. Every request would authenticate as a fake user. All data accessible to anyone.

That's the kind of thing that looks fine in development. You need it for end-to-end tests. But without a if (process.env.NODE_ENV === 'production') throw guard, it's a backdoor.

Other findings: CORS was misconfigured, the health endpoint leaked internal info, there was no SIGTERM handler (Azure sends SIGTERM on container shutdown, not SIGINT), rate limiting was per-process not shared across replicas, and production was using development logging.

All fixed. But the lesson was: ship, then harden. Getting the app deployed first, running the security audit second, was the right sequence. You can't audit what doesn't exist yet.

The token situation

The Copilot SDK needs a GitHub fine-grained personal access token with "Copilot" permission. Not a classic PAT — those are explicitly rejected. The token expires in 30 days.

This led to what I'll politely describe as a frustrating debugging session. The token worked, then didn't. MSAL auth worked in the browser but API calls returned 401. Multiple debugging rounds. The root cause was environment variables not loading — the server process was running without .env.local.

The broader problem: a 30-day token rotation with no automated renewal. For a demo project that's fine. For production, it needs Key Vault rotation policies or a GitHub App installation token instead.

The name

I wanted ResumeForge. Searched it — taken everywhere. resumeforge.app, resume-forge.com, multiple GitHub projects.

Resume Boutique was clean. No existing products. Has a curated feel — not a factory churning out resumes, but a boutique crafting them. Fits better anyway.

What it does now

Upload your resume as PDF. Paste a job posting (text or URL). The AI reads both, produces a complete tailored resume. You refine it through conversation — "make AWS experience more prominent," "remove the internship." Preview it. Download as PDF or DOCX.

It's deployed on Azure Container Apps. Authentication through Microsoft accounts. Data persists in Cosmos DB. Secrets in Key Vault with managed identity.

132 of 153 tasks complete. Phases 1 through 6 shipped. Phase 7 — user testing, content, interview prep — is what's left.

What I'd tell the interviewer

The honest version.

I chose Microsoft's stack because I wanted to demonstrate fluency, not because it was the easiest path. Cosmos DB over Postgres. Entra ID over Supabase Auth. Container Apps over Vercel. Key Vault over .env files.

The SDK was 5 weeks old and I shipped anyway. GitHub OAuth didn't work with Entra and I adapted. Nine bugs found in self-review, reverted, rebuilt. Four more found in production because I didn't test the core feature. 30 security issues caught in audit.

None of that is embarrassing. That's what building software actually looks like.

The thing I'd want the interviewer to know: I documented every session. 16 sessions, 48KB of learning log. Every decision, every error, every pivot. Not because I'm thorough — because I knew the journey was the point, not just the destination.

GitHub is going to onboard the next million builders onto AI. If I'm going to help with that, I should know what it feels like to be a builder on day one of a new SDK. Now I do.

Repo: github.com/tjp2021/resume-boutique