diff --git a/docs/README.skills.md b/docs/README.skills.md
index cdb41102c..a18e2d489 100644
--- a/docs/README.skills.md
+++ b/docs/README.skills.md
@@ -427,6 +427,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
| [update-llms](../skills/update-llms/SKILL.md)
`gh skills install github/awesome-copilot update-llms` | Update the llms.txt file in the root folder to reflect changes in documentation or specifications following the llms.txt specification at https://llmstxt.org/ | None |
| [update-markdown-file-index](../skills/update-markdown-file-index/SKILL.md)
`gh skills install github/awesome-copilot update-markdown-file-index` | Update a markdown file section with an index/table of files from a specified folder. | None |
| [update-specification](../skills/update-specification/SKILL.md)
`gh skills install github/awesome-copilot update-specification` | Update an existing specification file for the solution, optimized for Generative AI consumption based on new requirements or updates to any existing code. | None |
+| [upstash-redis](../skills/upstash-redis/SKILL.md)
`gh skills install github/awesome-copilot upstash-redis` | Use Redis over HTTP from serverless and edge runtimes with @upstash/redis, and add rate limiting with @upstash/ratelimit. Use when the user mentions Upstash Redis, needs Redis from a Next.js route handler or middleware, Vercel, Cloudflare Workers, Deno, or Bun without TCP connection pooling, or wants cache-aside with TTLs, a session store, counters, or a 429 rate limiter using fixed window, sliding window, or token bucket. DO NOT use for self-hosted or TCP Redis clients (ioredis, node-redis), Redis Cluster administration, or vector similarity search. | None |
| [vardoger-analyze](../skills/vardoger-analyze/SKILL.md)
`gh skills install github/awesome-copilot vardoger-analyze` | Use when the user asks to personalize the GitHub Copilot CLI assistant, adapt Copilot to their style, use vardoger, or analyze their Copilot CLI conversation history. Reads the local session directory at `~/.copilot/session-state/`, extracts recurring preferences and conventions, and writes a fenced personalization block into `~/.copilot/copilot-instructions.md`. Runs entirely on the user's machine via the local `vardoger` CLI (`pipx install vardoger`); no network calls and no uploads. Triggers: 'personalize my copilot', 'analyze my copilot history', 'tailor copilot to me', 'run vardoger', 'update my copilot instructions from history', 'make copilot learn my style'. | None |
| [vcpkg](../skills/vcpkg/SKILL.md)
`gh skills install github/awesome-copilot vcpkg` | Guide for setting up vcpkg in C++ projects, managing dependency versions, and cross-compiling. Covers manifest initialization, CMake and Visual Studio integration, classic-to-manifest migration, version pinning, baselines, overrides, triplets, and cross-compilation. Use when a user is working with vcpkg project setup, installation, version management, or cross-platform builds. For specialized tasks, additional references cover custom registries and overlay ports (references/registries.md), CI/CD and binary caching (references/ci.md), and troubleshooting and dependency lifecycle (references/troubleshooting.md). | `references/ci.md`
`references/registries.md`
`references/troubleshooting.md` |
| [verify-agent-action](../skills/verify-agent-action/SKILL.md)
`gh skills install github/awesome-copilot verify-agent-action` | Review a proposed AI-agent action or human-approval packet before execution. Use when an agent wants to run a consequential tool, command, deployment, message, purchase, credential operation, or data mutation; when checking whether approval still matches the exact action; or when auditing action evidence for forged results, parameter swaps, replay, correlated reviewers, missing evidence, expiry, or stale monitoring. Produce an evidence-based review only—never execute or authorize the action. | None |
diff --git a/skills/upstash-redis/SKILL.md b/skills/upstash-redis/SKILL.md
new file mode 100644
index 000000000..7290c9728
--- /dev/null
+++ b/skills/upstash-redis/SKILL.md
@@ -0,0 +1,188 @@
+---
+name: upstash-redis
+description: >
+ Use Redis over HTTP from serverless and edge runtimes with @upstash/redis, and
+ add rate limiting with @upstash/ratelimit. Use when the user mentions Upstash
+ Redis, needs Redis from a Next.js route handler or middleware, Vercel,
+ Cloudflare Workers, Deno, or Bun without TCP connection pooling, or wants
+ cache-aside with TTLs, a session store, counters, or a 429 rate limiter using
+ fixed window, sliding window, or token bucket. DO NOT use for self-hosted or
+ TCP Redis clients (ioredis, node-redis), Redis Cluster administration, or
+ vector similarity search.
+license: MIT
+compatibility: "@upstash/redis 1.x, @upstash/ratelimit 2.x, Node.js 18+ or any runtime with global fetch"
+metadata:
+ author: Upstash
+---
+
+# Upstash Redis Skill
+
+This skill covers the three things serverless apps most often need Redis for:
+caching, sessions, and rate limiting. The client talks to Redis over HTTP, so
+it works where a long-lived TCP connection does not (edge middleware, short
+lived functions). Follow the steps in order; each ends with a checkpoint.
+
+## Requirements and limitations
+
+- An Upstash Redis database (hosted service; usage-based pricing with a free
+ tier). Credentials are a REST URL and token from the database page.
+- Environment variables `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.
+- Every command is an HTTP request. Batch with `pipeline()` or `MGET`/`MSET`
+ when you issue many commands per request; avoid `KEYS *` in production.
+- Values are serialized automatically (objects, arrays, numbers round-trip).
+ Do not `JSON.stringify` before `set` or `parseInt` after `get`.
+
+## Step 1 — Install and create one client per module
+
+```bash
+npm install @upstash/redis @upstash/ratelimit
+```
+
+```ts
+// lib/redis.ts
+import { Redis } from "@upstash/redis";
+
+// Reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN
+export const redis = Redis.fromEnv();
+```
+
+Create the client at module scope, not inside the request handler, so
+ephemeral caches and pipelines can be reused across invocations.
+
+> **Checkpoint**: `await redis.ping()` returns `"PONG"`.
+
+## Step 2 — Cache-aside with TTL
+
+```ts
+import { redis } from "@/lib/redis";
+
+type User = { id: string; name: string; plan: "free" | "pro" };
+
+export async function getUser(userId: string): Promise {
+ const key = `user:${userId}`;
+ const cached = await redis.get(key);
+ if (cached) return cached;
+
+ const user = await db.users.findById(userId); // your data source
+ if (user) await redis.set(key, user, { ex: 3600 }); // 1 hour TTL
+ return user;
+}
+
+export async function updateUser(userId: string, patch: Partial) {
+ const user = await db.users.update(userId, patch);
+ await redis.set(`user:${userId}`, user, { ex: 3600 }); // write-through
+ return user;
+}
+
+export async function deleteUser(userId: string) {
+ await db.users.delete(userId);
+ await redis.del(`user:${userId}`); // invalidate
+}
+```
+
+Always set a TTL on cache entries; namespace keys (`user:123`, `session:abc`).
+
+> **Checkpoint**: second call to `getUser` returns without hitting the database
+> and `await redis.ttl("user:123")` is positive.
+
+## Step 3 — Sessions with sliding expiration
+
+```ts
+import { redis } from "@/lib/redis";
+
+const SESSION_TTL = 60 * 60 * 24; // 24 hours
+
+export async function createSession(userId: string, data: Record) {
+ const sessionId = crypto.randomUUID();
+ await redis.set(`session:${sessionId}`, { userId, ...data, createdAt: Date.now() }, { ex: SESSION_TTL });
+ return sessionId;
+}
+
+export async function getSession>(sessionId: string) {
+ const session = await redis.get(`session:${sessionId}`);
+ if (session) await redis.expire(`session:${sessionId}`, SESSION_TTL); // slide
+ return session;
+}
+
+export async function destroySession(sessionId: string) {
+ await redis.del(`session:${sessionId}`);
+}
+```
+
+Store the session id in an `HttpOnly; Secure; SameSite` cookie; never put the
+Redis token in client code.
+
+> **Checkpoint**: `getSession` after `createSession` returns the object with
+> `userId`; after `destroySession` it returns `null`.
+
+## Step 4 — Rate limiting a route handler
+
+```ts
+// app/api/search/route.ts (Next.js App Router; same pattern for any fetch handler)
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+
+const ratelimit = new Ratelimit({
+ redis: Redis.fromEnv(),
+ limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requests per 10 seconds
+ prefix: "ratelimit:search", // isolate keys per limiter
+});
+
+export async function POST(request: Request) {
+ const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "anonymous";
+ const { success, limit, remaining, reset } = await ratelimit.limit(ip);
+
+ if (!success) {
+ return new Response("Too Many Requests", {
+ status: 429,
+ headers: {
+ "X-RateLimit-Limit": String(limit),
+ "X-RateLimit-Remaining": String(remaining),
+ "Retry-After": String(Math.max(0, Math.ceil((reset - Date.now()) / 1000))),
+ },
+ });
+ }
+
+ // handle the request
+ return Response.json({ ok: true });
+}
+```
+
+- Identifier: use the user id or API key when authenticated; fall back to IP.
+- Algorithms: `Ratelimit.fixedWindow(n, "1 m")` (cheapest), `slidingWindow`
+ (smooth boundaries, default choice), `tokenBucket(refill, "10 s", max)`
+ (allows bursts). Windows accept `ms`, `s`, `m`, `h`, `d`.
+- Tiers: create one `Ratelimit` per tier with different `prefix` values.
+- Edge middleware / Cloudflare Workers with `analytics: true`: the result has a
+ `pending` promise; pass it to `context.waitUntil(pending)` so background work
+ finishes before the runtime exits.
+- `reset` is a Unix timestamp in milliseconds.
+
+> **Checkpoint**: the 11th request within 10 seconds returns 429 with a
+> `Retry-After` header; after the window it succeeds again.
+
+## Common pitfalls
+
+- **Creating clients inside handlers**: the limiter's in-memory
+ `ephemeralCache` only helps when the instance outlives the request.
+- **Manual JSON**: `redis.set("k", JSON.stringify(v))` then `redis.get` returns
+ an already-parsed object; double parsing throws.
+- **No TTL on cache keys**: memory grows until eviction; always pass `{ ex }`.
+- **Trusting `x-forwarded-for` blindly**: take the first hop, or use the
+ platform's IP helper, when behind a proxy.
+- **Forgetting `pending`** on edge runtimes with analytics or multi-region
+ limiters.
+
+## When NOT to use this skill
+
+- Long-running servers with a TCP Redis connection already in place: keep
+ ioredis/node-redis.
+- Vector search or RAG: use a vector database skill instead.
+- Sub-millisecond, in-process caching: use an in-memory LRU.
+
+## References
+
+- https://upstash.com/docs/redis/sdks/ts/overview
+- https://upstash.com/docs/redis/sdks/ratelimit-ts/overview
+- https://github.com/upstash/redis-js
+- https://github.com/upstash/ratelimit-js