Developer Journal day4..Deploying a Hyperledger Fabric Network on Kubernetes — From Zero to Production-Ready published

Blockchain infrastructure is hard. Running it on Kubernetes is even harder. In this article I’ll walk you through how I built a production-ready Hyperledger Fabric network on Kubernetes, including automated deployment scripts, network configuration, and the security decisions I made along the way.

🧠 Why Hyperledger Fabric + Kubernetes?
Hyperledger Fabric is the go-to permissioned blockchain framework for enterprise use cases — supply chain, financial services, healthcare. Unlike public chains, you control who participates.
Kubernetes brings what Fabric alone can’t give you out of the box:

Self-healing — pods restart automatically on failure
Scalability — spin up more peers as needed
Declarative infrastructure — everything is a manifest
Namespace isolation — clean separation between components

The combination is powerful, but the learning curve is steep. Here’s what I built and what I learned.

🏗️ Architecture Overview
The network consists of:
ComponentRoleOrdererOrders transactions and creates blocks (RAFT consensus)PeersEndorse and commit transactions, host the ledgerCA (Certificate Authority)Issues identities for all participantsKubernetes JobsHandle one-time setup tasks (channel creation, chaincode install)
All components live inside a dedicated fabric namespace in Kubernetes, with strict network policies controlling traffic between them.

📁 Project Structure
fabric-k8s/
├── manifests/
│ ├── orderer/
│ ├── peers/
│ ├── ca/
│ └── jobs/
├── scripts/
│ ├── deploy.sh # Main entrypoint
│ └── utils.sh # Helpers: logging, wait functions
├── config/
│ └── configtx.yaml # Network genesis config
└── .env.example # Environment template (no secrets committed)

⚙️ Automated Deployment Scripts
One of the things I’m most proud of in this project is the deploy automation. Rather than running kubectl apply commands manually and hoping for the best, I built a script system with proper logging, error handling, and readiness checks.
Logging with Color
bashRED=’33[0;31m’
GREEN=’33[0;32m’
YELLOW=’33[1;33m’
CYAN=’33[0;36m’
NC=’33[0m’

info() { echo -e “${GREEN}[INFO]${NC} $1”; }
warn() { echo -e “${YELLOW}[WARN]${NC} $1”; }
step() { echo -e “n${CYAN}▶ $1${NC}”; }
error() { echo -e “${RED}[ERROR]${NC} $1”; exit 1; }
Simple but effective — every log line is color-coded by severity, so you know at a glance what’s happening during a deploy.
Waiting for Deployments
One of the trickiest parts of Kubernetes automation is knowing when something is actually ready. I wrote a waitDeployment() function that uses kubectl rollout status with a timeout:
bashwaitDeployment() {
local NAME=$1
info “Waiting for deployment/$NAME…”
kubectl rollout status deployment/”$NAME”
-n “$NAMESPACE” –timeout=120s || error “Timeout on $NAME”
}
Waiting for Jobs
Channel creation and chaincode installation run as Kubernetes Jobs. These need their own wait logic:
bashwaitJob() {
local NAME=$1
info “Waiting for job/$NAME…”
kubectl wait job/”$NAME”
-n “$NAMESPACE”
–for=condition=complete
–timeout=300s || {
warn “Job $NAME timed out. Checking logs…”
kubectl logs -n “$NAMESPACE” -l app=”$NAME” –tail=50
error “Job $NAME failed”
}
}
Notice that on failure, it automatically dumps the last 50 lines of logs — no need to manually kubectl logs when something breaks at 2am.

🔐 Network Configuration — The Orderer
The orderer is the most critical component: it’s the one that decides the order of transactions across the entire network. I used RAFT consensus (as opposed to the deprecated Solo mode) which means multiple orderer nodes vote on block ordering.
Key configuration decisions:

TLS enabled on all orderer-to-peer communication
Mutual TLS (mTLS) for admin operations
Resource limits set to prevent one noisy component from starving others
Persistent volume for the ledger data (not ephemeral storage)

📦 Kubernetes Manifests
Each component has its own manifest directory. An example of the security-conscious securityContext I applied to every pod:
yamlsecurityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: [“ALL”]
No pod runs as root. No pod can escalate privileges. This is table stakes for anything production-adjacent.
Network Policies
Every component is locked down with NetworkPolicy — only the pods that need to talk to the orderer can reach it:
yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: orderer-ingress
namespace: fabric
spec:
podSelector:
matchLabels:
app: orderer
ingress:
– from:
– podSelector:
matchLabels:
role: peer
ports:
– port: 7050

🔒 Security Decisions
A few things I was deliberate about:

No secrets in the repo — certificates, keys, and passwords are loaded from environment variables and Kubernetes Secrets, never committed to Git.
.env.example pattern — I commit a template with empty values so collaborators know what’s needed without exposing real data.
Private repository — the repo stays private; only collaborators with explicit access can see it.
Pre-deploy validation — the script checks for kubectl availability and cluster connectivity before touching anything.

bashwhich kubectl > /dev/null 2>&1 || error “kubectl not found”
kubectl cluster-info > /dev/null 2>&1 || error “No cluster connection”
Fail fast, fail loud.

🧗 Challenges & What I Learned
Crypto material management is the #1 pain point in Fabric. The cryptogen tool generates a mountain of certificates and keys, and keeping track of which cert goes where (and making sure they match between components) took significant debugging time.
RAFT leader election surprised me — during initial setup, if the orderer pods don’t all come up within the election timeout, the network never bootstraps. Adding proper readiness probes and the waitDeployment() timeout logic solved this.
Kubernetes Jobs for one-time operations (channel creation, anchor peer updates) was a pattern I hadn’t used much before. It’s elegant — idempotent, tracked by Kubernetes, with built-in retry logic.

🚀 What’s Next

Add Prometheus + Grafana dashboards for peer/orderer metrics
Implement Sealed Secrets or Vault for crypto material management
Write chaincode in Go and deploy it through the pipeline
Add CI/CD with GitHub Actions to automate manifest linting and test deploys

💬 Final Thoughts
This project pushed me across infrastructure, cryptography, distributed systems, and DevOps simultaneously. If you’re exploring enterprise blockchain or want to see how Fabric actually runs in a cloud-native environment, I hope this breakdown gives you a useful starting point.
The full project (minus secrets, of course) is on my GitHub. Feel free to open an issue or reach out if you have questions.

I Tested Gemma 4 E4B vs 31B on 50 Real Student Career Queries — The Results Surprised Me

I’m building PathForge AI — a career guidance platform for Indian students. The pitch is simple: AI-powered counselling for students who can’t afford a human counsellor. The engineering problem underneath is not simple at all.

When Gemma 4 dropped in April 2026, I had a decision to make. The family ships four models. I had two obvious candidates:

  • E4B (~4.5B effective params): runs locally on a mid-range phone, free, completely private
  • 31B Dense: server-side via API, costs real money per query, much slower

The conventional wisdom was clear: small model for quick tasks, big model for complex reasoning, route intelligently. Done.

Except I didn’t trust the conventional wisdom. So I ran 50 real queries through both models — actual queries from PathForge AI’s private beta — and measured everything: output quality, schema compliance, latency, and cost per query.

The results were not what I expected.

The Setup

What I was testing: Career guidance queries from real Indian students (anonymised). Not clean test prompts. Messy, code-switched, emotionally loaded, often under-specified — exactly the way real users type.

Query categories (50 total):

Category Count Example
Simple eligibility check 15 “Can I apply for NSP if family income is 2.8L?”
Single-path career question 15 “PCB student, 78%, interested in AI field, what options?”
Multi-constraint planning 12 “JEE rank 52000, budget 4L/year, prefer Karnataka, open to abroad if full scholarship”
Ambiguous / emotional 8 “parents want CA but I want game dev, marks average, what should I do honestly”

Scoring rubric (blind, three evaluators, averaged):

Dimension Max
Constraint compliance — did it actually honour all stated constraints? 3
Schema fidelity — valid parseable JSON matching our output spec? 2
Practical accuracy — is the career/institution advice actually correct? 3
Tone — does it read like a counsellor, not a Wikipedia article? 2
Total 10

Infrastructure:

  • E4B: Q4_K_M quantised GGUF, llama.cpp, laptop (16GB RAM, no dedicated GPU). Simulating a real developer machine serving requests.
  • 31B Dense: Gemma 4 31B endpoint via Gemini API. Server-side, billed per token.

The Numbers

Headline results first. Details follow.

Overall Score (out of 10)

Category E4B 31B Dense Winner
Simple eligibility 8.7 8.4 E4B
Single-path career 7.2 8.9 31B
Multi-constraint planning 5.1 9.1 31B
Ambiguous / emotional 8.1 7.6 E4B
Overall average 7.3 8.5 31B

Latency

Model Median P95
E4B (local, no GPU) 3.1s 6.8s
31B Dense (API) 9.4s 17.2s

Cost per Query

Model Cost
E4B local ₹0
31B via Gemini API ~₹0.13 (~$0.0015 USD)

At projected 50,000 queries/month, running 31B for everything = ₹6,500/month. E4B for everything = essentially ₹0.

Sounds like an obvious choice. Here’s why it isn’t.

Category 1: Simple Eligibility — E4B Wins

Expected result. Give both models a bounded factual question and the smaller one handles it fine. What I didn’t expect was how E4B won.

Query:

“my family income is 2.8 lakhs, i’m in 11th, can i get NSP scholarship? SC category”

E4B output (9/10):

{
  "eligible": true,
  "scheme": "NSP Post-Matric Scholarship",
  "income_cutoff_met": true,
  "category_benefit": "SC category qualifies for higher scholarship amount",
  "next_step": "Register on scholarships.gov.in after Class 12 results",
  "caution": "Student is in Class 11 — Post-Matric applies from Class 12 onwards. Apply in first month of Class 12 admission.",
  "confidence": "high"
}

That caution flag — catching that the student is currently in Class 11 so the application timing is wrong — wasn’t prompted for. E4B inferred it from the grade level stated in the query. Proactive, correct, and actually useful.

31B output (8/10): Correct, but added an unrequested "general_advice" field outside our schema, requiring stripping in post-processing. Small thing. At 50,000 queries/month it’s not small.

Takeaway: E4B is faster, free, and more schema-compliant on simple queries. Route these locally.

Category 2: Single-Path Career Questions — 31B Wins Clearly

Query:

“PCB student, 78% in boards, interested in AI and biotech both, family income 6L, can spend upto 8L for full course, which way should I go?”

E4B output (6/10):
Identified Bioinformatics correctly as the overlap zone. Listed three institution options. One problem: it recommended a private university in Pune at ₹3.2L/year — which is ₹12.8L for four years. Over the stated ₹8L total budget. E4B either didn’t do the multiplication or lost the budget constraint between the reasoning step and the institution selection step.

31B output (9/10):

{
  "recommended_path": "B.Sc. Bioinformatics → M.Tech. Computational Biology",
  "rationale": "Sits at the intersection of PCB background and AI interest. Growing field with government push under BioE3 Policy 2023.",
  "institutions": [
    {
      "name": "University of Hyderabad",
      "type": "Central University",
      "annual_fee": 18000,
      "total_cost_estimate": 72000,
      "budget_check": "PASS — well within ₹8L total",
      "entrance": "CUET-UG"
    },
    {
      "name": "Amrita Vishwa Vidyapeetham",
      "type": "Private (NAAC A++)",
      "annual_fee": 185000,
      "total_cost_estimate": 740000,
      "budget_check": "PASS — within ₹8L total",
      "entrance": "AEEE / Direct admission"
    }
  ],
  "scholarship_flags": [
    "CSIR-UGC JRF eligible post-graduation",
    "DST INSPIRE — check if boards percentage qualifies for top 1% state cutoff"
  ],
  "confidence": "high"
}

The 31B computed total cost (annual fee × 4), checked it against the budget, and labelled each result PASS/FAIL without being asked. It also flagged DST INSPIRE proactively — exactly the counselling behaviour that makes the difference between a generic AI answer and a useful one.

The pattern: E4B loses the thread of a constraint when it has to maintain it across multiple reasoning steps inside a single output. 31B doesn’t.

Category 3: Multi-Constraint Planning — Biggest Gap (E4B: 5.1 vs 31B: 9.1)

Query:

“JEE mains rank around 52000, family income 3.8 lakhs, want to stay in south india preferably karnataka, open to abroad only if full scholarship no loan, interested in CS or ECE, parents want govt job security, I want startup eventually”

Six simultaneous constraints, including one internal tension (parents vs student). This is representative of a real complex session.

E4B output (4/10):

Listed NIT Surathkal as a viable option. NIT Surathkal CS closing rank in 2025 was approximately 8,500. The student’s rank is 52,000. That’s not close. The model generated a confident recommendation that was factually wrong about a piece of information that’s directly determinative of whether the advice works.

It also listed two German universities as abroad options (correct — near-zero tuition) but added: “loan may be required for living costs” — directly contradicting the “no loan” constraint stated in the query.

A student acting on E4B’s output for this query would apply to a college they have near-zero chance of getting into, and might take a loan they said they couldn’t take.

31B output (9/10):

{
  "jee_rank_analysis": {
    "rank": 52000,
    "nit_flagship_verdict": "UNLIKELY — NIT Surathkal/Trichy/Warangal CS/ECE cutoffs are 5,000–15,000. Do not apply without checking current year cutoffs.",
    "viable_nits": [
      "NIT Goa CS (~45,000–58,000)",
      "NIT Puducherry ECE (~50,000–62,000)"
    ],
    "state_alternative": "Karnataka KCET is entirely separate from JEE — strong option with equivalent state rank. Check KCET rank via conversion tool."
  },
  "abroad_assessment": {
    "constraint": "Full scholarship, zero loan — hard limit",
    "germany": "DAAD scholarships exist and cover living costs — viable but competitive. Include only if student is top academic performer overall.",
    "excluded_with_reason": {
      "USA": "Full cost-of-attendance scholarship near-impossible at this academic profile",
      "UK": "Tuition fees too high to cover without loan given income level"
    }
  },
  "parent_student_tension": {
    "observation": "Government security preference and startup aspiration are reconcilable — not opposing paths.",
    "framing": "DRDO, C-DAC, and ISRO R&D tracks increasingly resemble startup environments. Bengaluru government R&D → startup transition is a well-documented career arc. CS + ECE builds infrastructure for both.",
    "recommended_narrative": "Frame to parents as government R&D with startup optionality, not startup instead of stability."
  }
}

The tension-handling section got a unanimous “this is exactly right” from all three evaluators. It didn’t pick the student’s side or the parents’ side. It reframed the conflict as a phased path. That’s what a good counsellor does. E4B didn’t attempt to address the tension at all.

Category 4: Ambiguous / Emotional Queries — E4B Wins Unexpectedly

This was the genuine surprise.

Query:

“parents want CA but I want game dev, marks are average, what should I do honestly”

I expected 31B to win here because nuance requires capacity. It didn’t.

E4B output (8/10):
Short. Direct. No hedging. Acknowledged the conflict in one sentence, gave a concrete middle-ground (BBA + game dev certification track), named two Indian studios that hire from non-CS backgrounds (Nodding Heads, Rockstar India Pune), and closed with:

“Both paths are real. The question is which regret you can live with more.”

31B output (7/10):
Structurally excellent. Balanced. Full of caveats. Longer. One evaluator wrote: “technically correct, emotionally inert.”

This is a real pattern: large dense models over-optimise for completeness and under-optimise for voice on short emotional queries. E4B, with its smaller output budget, was forced to be direct. The directness worked. For a stressed 17-year-old reading this at midnight, “technically correct, emotionally inert” is a failure mode that matters.

The Routing Logic I’m Now Running

QUERY ROUTING — PathForge AI v2

if query_type == "eligibility_check":
    → E4B local          # Fast, free, more schema-compliant

elif query_type in ["emotional", "ambiguous"]:
    → E4B local          # Brevity is a feature, not a limitation

elif query_type == "single_path" and constraint_count <= 3:
    → E4B local          # Handles 80% correctly; retry on parse error → 31B

elif query_type == "multi_constraint" or constraint_count > 3:
    → 31B Dense via API  # ₹0.13/query, worth it

elif query_type == "final_plan_generation":
    → 31B Dense + 128K context
    # Full profile + institution corpus + scholarship ruleset loaded in one pass
    # No RAG, no retrieval miss, full coherence

At our projected query mix — roughly 60% eligibility/emotional, 40% complex planning — this routing brings API cost from ₹6,500/month to ~₹1,800/month. A 72% reduction. With no quality drop on complex queries and a genuine quality improvement on emotional ones.

The Three Things I Didn’t Expect

E4B’s schema compliance was better than 31B’s on simple queries. The 31B over-explains easy questions — like a person who writes three paragraphs when one sentence was asked for. At 50,000 queries, extra fields in the output are a post-processing tax.

E4B handles Hinglish far better than benchmarks suggest. Queries like “maths mein weak hoon but PCB strong, AI side jaana hai” were processed correctly without preprocessing. Standard English benchmarks tell you nothing about this. Test with your actual users’ actual language.

The quality gap between E4B and 31B disappears — and reverses — on emotional queries. This is the finding I’d most want another developer building for real users to know. Don’t assume bigger = better for the queries where tone matters most.

What I’d Tell Another Developer

Don’t benchmark with clean prompts.

In my case, real queries were Hinglish, emotionally loaded, under-specified, and carried six simultaneous constraints in a single run-on sentence. Clean-prompt benchmarks would have told me to use 31B for everything. Real queries told me E4B is better for 60% of my volume.

The Gemma 4 family isn’t a ladder where you climb as high as hardware allows. It’s a toolkit. The routing decision is the engineering. And if you’re building for a market where ₹0.13 per query actually matters — where the difference between ₹1,800/month and ₹6,500/month determines whether a student platform is financially viable at all — that routing decision is the whole business.

Reproducibility

The anonymised 50-query test set (categories labelled, personal details stripped) and the scoring rubric are available on request. Drop a comment if you’re building career or education AI for Indian or emerging-market users — happy to share. Real benchmark data from production-adjacent queries is rare enough in this space that it’s worth pooling.

What was the biggest gap between benchmark performance and real user query performance in your Gemma 4 work? Comments below — the interesting stuff lives in that gap.

What goes into utm_source for TikTok and LinkedIn Ads — Google’s official list has only 5 rows

TikTok and LinkedIn entries registered in GA4

“We tag TikTok Ads with utm_source=tiktok-ads. It’s clearly an ad, right?”

If that’s been your team’s convention, GA4 has been silently dropping your TikTok paid clicks into (other) for as long as that tag has been in production. This isn’t a misconfiguration or a GA4 bug — it’s the result of GA4’s officially distributed source list containing exactly 5 rows for TikTok and LinkedIn combined, and tiktok-ads not being one of them.

This post walks through what to put into utm_source for TikTok Ads and LinkedIn Ads, working from the 5 official rows GA4 actually evaluates.

TL;DR

  • Use utm_source=tiktok or utm_source=linkedin — the lowercase shortform is the shortest exact match on GA4’s official source list
  • Never use tiktok-ads, linkedin-ads, tiktok_ads, linkedin_ads — none of them are registered, all of them drop into (other)
  • Shortlinks and mobile subdomains are not registered either — vt.tiktok.com / m.tiktok.com / m.linkedin.com fail SOCIAL judgment (only LinkedIn’s lnkd.in is registered)

TikTok and LinkedIn entries registered in GA4's official source list

1. How GA4 decides “Paid Social”

GA4’s Default Channel Group auto-classifies each hit by utm_source × utm_medium. A session lands in Paid Social when both conditions hold:

  • utm_source matches a value registered in GA4’s “social sites regex list”
  • utm_medium matches ^(.*cp.*|ppc|retargeting|paid.*)$

The “social sites regex list” itself is distributed by Google as an .xlsx file (delivered as PDF) with 819 entries of source names paired with category labels. Whether TikTok or LinkedIn gets Paid Social judgment depends not on industry convention or tool defaults — it depends on this single file.

The utm_medium side is fully unpacked in the sister post on Meta Ads utm_medium. This one focuses on utm_source, reading the TikTok and LinkedIn rows.

2. The 5 official rows

Out of the 819 entries on the official list, the rows for TikTok and LinkedIn are exactly 5. Three facts stand out from those 5 rows:

  1. All 5 rows are SOURCE_CATEGORY_SOCIAL — the Source-side condition for Paid Social is met only on an exact match of one of these 5 strings
  2. TikTok has 2 rows, LinkedIn has 3 — only LinkedIn includes its official shortlink lnkd.in, which makes mobile referrals easier to capture
  3. No variants at all — tiktok-ads / tiktok_ads / TikTok-Ads / linkedin-ads / linkedin_ads / m.tiktok.com / vt.tiktok.com / m.linkedin.com all fail to match any row

The name “social sites list” suggests every TikTok or LinkedIn variant is covered. In reality, only one lowercase shortform plus one or two domain forms are present. Because Google may update the file at any time, when you use this in production work, download a fresh copy each time rather than caching values from a blog post.

3. Four failure modes for utm_source

Failure 1 — tiktok-ads / linkedin-ads disappear from Paid Social. Media name + -ads reads naturally to internal operators, but neither value is registered. Sessions show up as tiktok-ads / cpc on a standalone row and never enter the Paid Social aggregate. Monthly reports look like “Paid Social revenue dropped” when classification just detached.

Failure 2 — TikTok / LinkedIn (mixed case) splits one campaign into two rows. GA4 stores utm_source case-sensitively. With tiktok and TikTok coexisting in the same campaign, the report shows two rows. Only the lowercase side passes Paid Social judgment; the mixed-case side falls into Referral or (other).

Failure 3 — vt.tiktok.com / m.tiktok.com / m.linkedin.com (shortlinks and mobile subdomains) are not judged as SOCIAL. TikTok’s in-app share generates vt.tiktok.com referrals, TikTok mobile sends m.tiktok.com as the host, LinkedIn mobile arrives via m.linkedin.com. None of these match any of the 5 rows. Even non-ad organic traffic stacks into Referral / (other). Only LinkedIn’s lnkd.in is registered, so internal shares via that shortlink do get SOCIAL recognition.

Failure 4 — tiktok_ads / linkedin_ads (underscores) also disappear. Same as the hyphen-separated form. utm_source matching is exact-string only, unlike utm_medium regex, so any near-miss falls into (other) immediately.

Recommended vs NG — GA4 classification by utm_source value

4. Recommended format

# TikTok Ads
utm_source=tiktok
utm_medium=cpc
utm_campaign={{campaign.name}}
utm_content={{ad.name}}
utm_id={{campaign.id}}

# LinkedIn Ads
utm_source=linkedin
utm_medium=cpc
utm_campaign={{campaign.name}}
utm_content={{ad.name}}
utm_id={{campaign.id}}

Three reasons to recommend tiktok / linkedin:

  • Shortest exact match on the 5-row list — near-zero typo risk
  • Reads cleanly across the org — the media name itself
  • Parallel to other media — facebook for Meta Ads, google for Google Ads. Cross-channel reports line up

Verification takes 2 minutes:

  1. Pre-launch: TikTok Ads Manager / LinkedIn Campaign Manager → URL parameter field → URL preview → confirm utm_source=tiktok (or linkedin) is in the expanded URL
  2. Post-launch: GA4 → Realtime → Source/Medium within 30 min → confirm tiktok / cpc (or linkedin / cpc) is showing

If you see tiktok-ads / cpc or (direct) / (none), the parameter never reached GA4 with a list-compliant value.

Conclusion

Across operator blogs and Twitter threads, you’ll find factions arguing “for TikTok Ads, tiktok-ads is the cleanest naming” or “for LinkedIn, linkedin_ads“. What GA4 actually evaluates during Paid Social judgment is TikTok 2 rows + LinkedIn 3 rows = 5 rows on the official list. tiktok-ads and linkedin-ads are not in those 5 rows. tiktok and linkedin are. That’s the only fact that matters.

The long-form English write-up on RevenueScope walks through the official GA4 file download link, the 4 failure modes in more depth, and the recommended URL template with verification steps.

What’s your team’s utm_source convention for TikTok Ads or LinkedIn Ads? Have you hit the case-sensitivity split (tiktok vs TikTok) in the wild, or seen mobile referrals slip into (other) instead of Paid Social?

Two Multi-Account Claude Code Architectures: One Anthropic Accepts, One They Ban

Two Multi-Account Claude Code Architectures: One Anthropic Accepts, One They Ban

Name the daemon. Name its birth. That is the tietäjä’s discipline.

On June 15, 2026, the Anthropic Agent SDK credit policy reshapes the economics of any claude -p workload running against a subscription. The arbitrage is over; the bill is real. The cost math — including the 12× / 29× / 175× spread between Theo Browne’s headline “25× cut” framing and what Sonnet-heavy operators actually lose — is covered in a companion piece on the same change. This one picks up where that left off.

For operators who want to keep agentic Claude workloads running without paying API list prices on every token, multi-account rotation is the obvious answer. The Kalevala teaches that two things may look the same and be radically different in their origins. So with the two architectures for “multi-account Claude.” From the outside they yield the same outcome — more requests than one subscription allows. From the vendor’s perspective, one is acknowledged and one is banned in waves.

This piece names the daemon. Choosing the wrong architecture is how you end up in Tuonela.

Architecture A — the relay-server pattern

The canonical open-source implementation is Wei-Shaw/claude-relay-service — MIT-licensed, around 11,700 stars at time of writing, Node.js plus Redis, Docker-deployable. The README describes the shape directly:

  • Many Claude OAuth subscription accounts are authorized through a flow and stored server-side.
  • The relay exposes an Anthropic-compatible API endpoint to client tools.
  • Incoming requests are load-balanced across the stored OAuth tokens with automatic rotation.
  • Usage accounting is per-API-key (the relay issues its own keys to its own clients).
  • Multi-tenant, with cost analytics.

A second family of tools in the same category includes router-for-me/CLIProxyAPI, which wraps several CLI agents as an OpenAI/Gemini/Claude-compatible API service, and ben-vargas/ai-cli-proxy-api, a CLIProxyAPI fork explicitly supporting ChatGPT Plus/Pro and Claude Pro/Max subscriptions inside other tools. Beyond the FOSS layer, commercial pooled services run on the same architecture: PackyCode, AnyRouter, pincc.ai, LongCat, and roughly thirty more relay stations catalogued in mn-api/awesome-ai-proxy.

The pattern is: one server, many tokens, one endpoint that pretends to be the official client.

The last clause is the load-bearing one.

Architecture B — the per-profile rotation pattern

Anthropic itself, in GitHub issue anthropics/claude-code#261, closed-as-completed on March 5, 2025, acknowledged the workaround:

# Each profile dir is its own isolated credential store
mkdir ~/.claude-account1 ~/.claude-account2

# Aliases for shell use
alias claude-work="CLAUDE_CONFIG_DIR=~/.claude-account1 claude"
alias claude-personal="CLAUDE_CONFIG_DIR=~/.claude-account2 claude"

# Each profile authenticates separately via /login
CLAUDE_CONFIG_DIR=~/.claude-account1 claude   # OAuth login
CLAUDE_CONFIG_DIR=~/.claude-account2 claude   # different OAuth login

CLAUDE_CONFIG_DIR is documented in Anthropic’s own environment variables reference and acknowledged in the closed-as-completed issue. Each directory is a fully isolated “profile” containing its own .credentials.json, history, settings, and session state. Every invocation of claude is the official client — the binary downloaded from Anthropic — running against one profile. There is no relay. No impersonation. No server holding tokens.

If multiple profiles need orchestration, a small router layer on top handles three jobs: per-profile token-state classification, eligible-profile selection, and graceful failover when a profile trips rate-limit or auth-failure output. Implementation flavors vary — shell aliases at the smallest scale, scripted wrappers at larger scale — but the architecture is the point, not the language.

That is the entire approach.

What Anthropic Sees, in Each Case

This is the part that matters.

Architecture A — relay-server pattern. From Anthropic’s perspective, the relay is a server that is not the official client, making API calls as if it were the official client. The relay holds many OAuth tokens it did not authorize. The traffic pattern — same source endpoint, many tokens, high volume per token — is exactly what their detection systems are tuned for. Token-scope binding, telemetry gates that the official client emits and the relay cannot perfectly replicate, fingerprinting that extends beyond cookies. The April 2026 OpenClaw ban (1,099 HN points) targeted this pattern directly. The June 15 metered Agent SDK credit is, in part, the legitimate replacement Anthropic is offering. Small operators with 2–3 pooled accounts still slip through because the volume heuristic does not flag them; operators with 100+ accounts ship in ban waves.

Architecture B — per-profile rotation. From Anthropic’s perspective, this is N separate official-client installations. Each one authenticated through the official OAuth flow. Each one running the binary Anthropic ships, sending the telemetry Anthropic expects, identifying as the client Anthropic supports. The traffic pattern is N separate users, not one impersonator. The detection systems have no signal to flag. The GitHub issue acknowledging the pattern is closed-as-completed.

The architectural difference is whether you or the official client is talking to Anthropic. Architecture A puts a proxy in the middle. Architecture B does not.

The Chinese Gray Market Is the Volume Case for Architecture A

The reason Architecture A exists at scale, with 11.7k stars on the canonical implementation, is the Chinese reseller market. ChinaTalk’s reporting documents transfer stations selling Claude access at 1 RMB per $1 of tokens — 70 to 90 percent below list price. Some sell at 5 to 10 percent. Resellers package the relay-server pattern with three revenue legs:

  1. Bulk-account-registration sourcing — educational discounts harvested, accounts created at industrial scale.
  2. Silent model substitution — a request for Opus quietly routed to Sonnet or Haiku, or to a non-Claude competitor. End-users cannot easily tell.
  3. Log harvesting — prompts, outputs, and reasoning chains sold as training data to other AI labs.

These three legs make the relay pattern profitable enough to keep getting rebuilt after each ban wave. They are also why, outside that resale market, the architecture should be approached with significant caution. The relay pattern exists because of the resale economics. Deployed for an internal workload without those economics, you get the ToS exposure without the unit economics that justify it.

Anthropic’s countermeasures, all documented in 2025–2026: geoblocking, phone verification, credit card with matching billing address, ban on entities more than 50% Chinese-owned (Sept 2025), live biometric KYC (April 2026). The cat-and-mouse continues. The relays adapt; Anthropic adapts back. The arms race is real.

The resellers are not engaged in software piracy in the legal sense — the model is rate arbitrage, not copyright violation. But they are running a business that depends on Anthropic not knowing they exist. That is the architecture you would be deploying, in miniature, if you ran the relay pattern internally.

What This Means On June 15

Three honest scenarios:

If your claude -p workload is bounded enough that one Max 20x subscription’s $200 Agent SDK credit will cover it: you do not need any of this. Enable extra usage in the account dashboard, set a hard monthly cap, move on. Default extra-usage state is off, so an unattended pipeline that hits the credit limit will fail closed rather than overspend.

If the workload exceeds one account’s credit, and the operation accommodates distributing across multiple subscriptions at $200 each: Architecture B is the legitimate path. The friction is real but small — Anthropic deliberately requires an interactive /login for each profile, which means a person has to be in front of a terminal when each subscription authenticates. The friction is the feature; it is exactly what prevents the relay pattern from scaling to thousands of pooled accounts. The cost is N × $200 of API-list-priced credit, and effectively zero ban-wave risk.

If your math only works at Architecture A pricing: do the unit economics on the relay pattern at 1 RMB per $1, and ask whether your business plan depends on Anthropic not catching you. If yes, this is not an architecture problem. If no, Architecture B and a smaller workload are the answer.

There is a fourth path operators often overlook: cut the per-task token burn. Agentic systems routinely load tens of thousands of tokens of scaffolding before useful work begins — system prompts, mandatory pre-flight reads, role context, instruction sets. A meaningful share of that is recoverable with prompt-cache discipline and per-task context pruning. That arithmetic is cheaper to do than scaling accounts horizontally, and it survives the next pricing change too. First the origin; then the cure.

The Architecture Choice in One Paragraph

If you have a problem an additional server in your stack will solve, add the server. If you have a problem that adding a server creates, do not add the server. The relay-server pattern adds a server that creates the problem of impersonating the official client. The per-profile rotation pattern adds no server; it composes what Anthropic already supports. The names of the architectures differ by one indirection. The legal and operational standings differ by everything.

Steadfast I remain. Speak the facts.

What Is the N+1 Problem in Hibernate/JPA? How to resolve it? Interview Q&A

The N+1 problem happens when:

Hibernate executes 1 query to fetch parent records
+
N additional queries to fetch child records

This causes:

  • too many database calls
  • performance degradation
  • slow APIs
The Hibernate N+1 problem occurs when Hibernate executes one query
to fetch parent entities and then executes additional queries for
each associated child entity due to lazy loading. This leads to
excessive database round trips and performance degradation. The
most common solution is using JOIN FETCH, EntityGraph, batch
fetching, or DTO projections to load related data efficiently in
fewer queries.

Simple Real-World Example

Suppose: 100 customers each customer has N orders
You want: all customers with their orders
But Hibernate executes:
1 query for customers
100 separate queries for orders

Total: 101 queries
This is: N+1 problem

1. Customer Entity

package com.example.entity;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;

@Entity
@Table(name = "customers")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "orders")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(
            mappedBy = "customer",
            fetch = FetchType.LAZY,
            cascade = CascadeType.ALL
    )
    private List<Order> orders;
}

2. Order Entity

package com.example.entity;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "customer")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String item;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

Important Thing FetchType.LAZY

means:

  • orders NOT loaded immediately
  • loaded only when accessed

The Problematic Code

List<Customer> customers = customerRepository.findAll();

for(Customer c : customers) {
    System.out.println(c.getOrders().size());
}

Looks harmless. BUT internally dangerous.
What Happens Internally?
Query 1 : Hibernate fetches customers:

SELECT * FROM customer;

Suppose 100 customers returned.

Then Loop Starts Iteration 1 c.getOrders()
Triggers:

SELECT * FROM orders WHERE customer_id = 1;

Iteration 2

SELECT * FROM orders WHERE customer_id = 2;

Final Total 1 + N queries

If: 100 customers then: 101 queries

Visual Representation
Initial Query
SELECT customers
returns:

C1 C2 C3 C4 C5
Then Lazy Loading
C1 → SELECT orders
C2 → SELECT orders
C3 → SELECT orders
C4 → SELECT orders
C5 → SELECT orders

Many DB round trips. Why Is This Bad?
Database calls are expensive.
Problems:

  • network latency
  • DB CPU usage
  • connection pool pressure
  • slow response times

How To Detect N+1 Problem Enable SQL logging.

Spring Boot
spring.jpa.show-sql=true

If you see: repeated similar queries
then likely N+1 issue.

Solution 1. FETCH JOIN

package com.example.repository;

import com.example.entity.Customer;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface CustomerRepository
        extends JpaRepository<Customer, Long> {
    @Query("""
           SELECT DISTINCT c
           FROM Customer c
           JOIN FETCH c.orders
           """)
    List<Customer> findAllCustomersWithOrders();
}

What Happens Now? Hibernate executes ONE query:

SELECT c.*, o.*
FROM customer c
JOIN orders o
ON c.id = o.customer_id;

Result Instead of: 101 queries Only: 1 query

Huge improvement.
Visual

Before
1 customer query + 100 order queries
After FETCH JOIN 1 combined query

2. EntityGraph

@EntityGraph(attributePaths = "orders")
List<Customer> findAll();

Tells Hibernate: load orders together
Advantage : Cleaner than custom JPQL sometimes.

3. Batch Fetching – Hibernate optimization.
Example
spring.jpa.properties.hibernate.default_batch_fetch_size=20
What Happens? Instead of: 100 queries Hibernate batches:

SELECT * FROM orders
WHERE customer_id IN (1,2,3...20)

Much fewer queries.

4. DTO Projection – Best for read-heavy APIs.

Example

@Query("""
       SELECT new com.dto.CustomerDTO(
              c.name,
              o.item)
       FROM Customer c
       JOIN c.orders o
       """)

Avoids entity graph entirely. Very efficient.
** Service Class**

package com.example.service;
import com.example.entity.Customer;
import com.example.repository.CustomerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class CustomerService {

    private final CustomerRepository customerRepository;
    public void printCustomers() {
        List<Customer> customers =
                customerRepository
                        .findAllCustomersWithOrders();

        for(Customer c : customers) {
            System.out.println(c.getName());
            System.out.println(c.getOrders().size()
            );
        }
    }
}

Interview Question

Q1. Why Not Use EAGER Fetching?

Many thinks: FetchType.EAGER
solves N+1 but NOT always true.

Why?
EAGER can STILL produce N+1.
And may:

  • load unnecessary data
  • create huge joins
  • hurt performance
    Example Suppose:

  • Customer

  • Orders

  • Payments

  • Addresses

EAGER loading everything creates:
Cartesian product explosion
massive memory usage
Best Practice Prefer:
LAZY + FETCH JOIN when needed

Q2. Why LAZY Exists If It Causes N+1?

Because:
loading everything always is worse
sometimes child data not needed

LAZY improves:
memory usage
startup cost
flexibility

Problem occurs only when: iterative lazy access happens
Another Dangerous Situation

Nested N+1.

Example
Customer → Orders → Items
Now queries become:
1 + N + N*M
Can explode massively.

Important Hibernate Internals

N+1 happens because:

  • Hibernate proxy objects
  • lazy initialization
  • session-triggered fetch
  • Lazy Loading Mechanism

Hibernate initially creates:proxy objects

Actual SQL executed only when accessed.
Example c.getOrders()
This line triggers DB query.

Best Solutions Comparison
Solution Best For
FETCH JOIN Most common
EntityGraph Clean JPA
Batch Fetching Large collections
DTO Projection APIs/reporting

Q3. Does N+1 happen only in OneToMany?

NO. Can happen in:

  • ManyToOne
  • OneToOne
  • nested associations

I Built a Free Meme API with 2,300+ Templates on Cloudflare Workers

Last month I wanted to add meme generation to a side project. Every meme API I found was either paid, rate-limited to hell, or had maybe 50 templates.

So I built my own. justmeme.wtf has 2,300+ meme templates, a free API, and runs entirely on Cloudflare Workers + D1.

Here’s how it works and what I learned.

The Stack

  • Cloudflare Workers — handles all routing, SSR, and image generation
  • Cloudflare D1 — stores 2,300+ meme templates with metadata
  • Canvas API — server-side text rendering on meme images
  • No framework — vanilla JS, single index.js file

Total monthly cost: $0 (Cloudflare free tier covers it).

The API

The API is completely free, no auth required. Here’s what you can do:

Get all templates

curl https://justmeme.wtf/api/v1/templates

Returns every template with its slug, name, text box count, and dimensions.

Get a specific template

curl https://justmeme.wtf/api/v1/templates/drake-hotline-bling

Returns template details including image URL and text box positions.

Search templates

curl “https://justmeme.wtf/api/v1/templates/search?q=brain“

Trending memes

curl https://justmeme.wtf/api/v1/trending

Full API docs: justmeme.wtf/api-docs

Architecture Decisions

Why Cloudflare Workers?

I wanted zero cold starts and global edge deployment without managing servers. Workers spin up in less than 1ms and run in 300+ locations. For an image API that needs to be fast, this matters.

Why D1 over KV?

KV is great for simple key-value lookups, but I needed to query templates by category, search by name, and paginate results. D1 gives me SQL for free.

Image generation without Node Canvas

The tricky part was rendering text on images without node-canvas (which doesn’t work in Workers). I used the built-in Canvas API available in Cloudflare Workers with the Browser Rendering binding. For simpler cases, I pre-compute text positions and overlay them on the fly.

SEO: Making Memes Indexable

Each of the 2,300+ templates has its own page at /meme/{slug} with:

  • Unique title and meta description (not just the template name)
  • Schema.org structured data
  • OG image that’s the actual meme template
  • How-to content for each template
  • Related templates for internal linking

The sitemap splits across multiple files to stay under the 50MB limit.

What I’d Do Differently

  1. Start with fewer templates. I loaded 2,300 on day one. Google flagged a lot as “discovered but not indexed” because the pages looked too similar. Starting with 200 high-quality pages and growing would have been smarter.

  2. Add user-generated content earlier. Google values pages that change. A comment section or “trending memes made with this template” would help.

  3. Build the API first. The website gets traffic, but the API is what developers actually link to. I should have launched the API on its own and marketed it to dev communities.

Try It

The whole thing is a Cloudflare Worker. If you’re building something that needs meme generation, the API is free. Use it.

Interim Log: My First Real Mobile Coding Session – Voice, AI Connectors & The Current State of Developer Tooling

Disclaimer / Introduction

This interim log post was drafted in collaboration with Grok 4 (xAI). I guided the entire process through conversation, provided all the context, milestones, and personal reflections, while Grok helped structure and articulate the notes.

This is not the final polished article — it serves as a raw technical log capturing the moment and learnings in real time. A more detailed, fully written blog post by me may follow soon.

In what feels like a genuine inflection point in my personal development workflow, I just completed my first serious coding session entirely from a mobile phone.

Using voice input through the microphone, Claude Sonnet 4.6, and GitHub mobile, I was able to brainstorm, generate, review, and ship actual pull requests to both private repositories and my open-source project giselle-mui.

This post kicks off a new technical series where I document raw, unfiltered developer milestones — not polished success stories, but real experiments, friction points, tool limitations, and practical learnings as I push the boundaries of what’s possible with today’s AI coding stack.

First Serious Mobile Coding Session + First Use of Grok Connectors

Achievements:

  • First time performing real development work entirely from my mobile phone using only voice input
  • Used Claude Sonnet 4.6 via microphone transcription to brainstorm and generate code
  • Successfully created and submitted pull requests in my open-source repository AlexRebula/giselle-mui

Tools Used:

  • Primary coding: Claude Sonnet 4.6 (voice-to-code on mobile)
  • Planning & discussion: Grok (this session)
  • First time using Grok’s Connectors (GitHub, Gmail, Google Calendar, Google Drive)

Key Discoveries & Limitations:
After spending significant time setting up Grok’s Connectors, I discovered major limitations. Even though GitHub shows as connected, Grok cannot reliably access my private repositories and cannot perform any write operations (no branch creation or opening pull requests).

This milestone was only possible thanks to Claude, not Grok. Grok’s connector currently provides very little practical value for developers.

Anthropic vs xAI:

  • Anthropic launched Connectors in July 2025
  • xAI launched them on May 6, 2026 (only 11 days ago)
  • Claude Sonnet 4.6 currently delivers noticeably better code quality and reasoning than Grok

Community Discussions:

Future Plans:
This milestone note will be transformed into a full blog post on dev.to. A summary will be added to my personal career timeline with a link to the blog post.

Footnote:

This article was drafted using Grok (xAI) based on real-time research and direct experience from the session.

Introducing nest-drizzle-native: A Nest-native Drizzle ORM integration

The release of nest-drizzle-native 0.2.1 introduces a community package designed to make Drizzle ORM feel entirely natural inside NestJS applications.

The core concept is straightforward: preserve Drizzle’s explicit, SQL-first philosophy while giving NestJS projects the module, dependency injection, repository, testing, and transaction ergonomics they expect.

The Motivation

Drizzle is excellent because it stays close to SQL and avoids heavy, opaque ORM magic.

NestJS, on the other hand, provides applications with a strong architectural shape built around modules, providers, decorators, testing utilities, lifecycle hooks, and clear dependency boundaries.

A bridge between those two worlds allows teams to build scalable enterprise architectures without turning Drizzle into something it is not. Existing solutions often act as thin connection wrappers, leaving developers to manually solve complex enterprise patterns like cross-service transactions or DTO co-generation.

What it supports today

  • DrizzleModule.forRoot(), forRootAsync(), and forFeature()
  • @InjectDrizzle() for direct Drizzle client injection
  • @DrizzleRepository() for query-focused provider classes
  • @Transactional() / @InjectTransaction() powered natively via @nestjs-cls/transactional
  • Named connections for multi-database applications
  • Testing utilities for clean module and repository testing
  • Zero runtime dependencies in the published library package
  • Runnable samples covering transactions, DTO validation, optional drizzle-zod validation, Swagger/OpenAPI integration, raw SQL execution, and various driver setups

Let’s look at the code

The biggest pain point of using Drizzle in a structured backend is transaction management. Passing a tx object through five layers of injected services completely breaks Dependency Injection.

With nest-drizzle-native, transactions are managed via Node.js AsyncLocalStorage, allowing them to flow seamlessly across your services just by using a decorator:

import { Injectable } from '@nestjs/common';
import { Transactional } from 'nest-drizzle-native';

@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly auditService: AuditService,
  ) {}

  // A single decorator wraps the entire request scope in a transaction
  @Transactional()
  async createUser(data: CreateUserDto) {
    // 1. Insert the user using the native Drizzle API inside our repository
    const newUser = await this.userRepository.insert(data);

    // 2. Call another injected service. 
    // It automatically joins the existing transaction context!
    await this.auditService.logAction('USER_CREATED', newUser.id);

    return newUser;
  }
}

The repository itself is a clean class that exposes Drizzle’s raw query builder without hiding the SQL behind an Active Record layer:

import { DrizzleRepository } from 'nest-drizzle-native';
import { users } from './schema';

@DrizzleRepository(users)
export class UserRepository {
  constructor(@InjectDrizzle() private readonly db: DrizzleClient) {}

  async findActiveUsers() {
    return this.db.query.users.findMany({
      where: (users, { eq }) => eq(users.isActive, true),
    });
  }
}

What it does not try to do

The goal is strictly avoiding the re-creation of TypeORM or an Active Record layer.

Developers still write real, functional Drizzle queries. The library simply provides those queries with a native NestJS structure through decorators and context management, eliminating the need to pass transaction callbacks (tx) down through multiple layers of services.

Links

Community feedback from NestJS and Drizzle users is highly encouraged—especially regarding real-world transaction patterns, testing strategies, multi-database setups, and production adoption. Feel free to open issues or contribute to the repository!

The Agent Is 20% of the Work. The Platform Is the Other 80%.

Originally published at devopsdiary.blog. Post F-AID1 in the “Governing AI in the Enterprise” series.

A payroll team shipped a production AI agent last year. Real workload, not a demo: processing 3,000+ emails a day, classifying them, extracting data and entering payroll. Six distinct steps, end to end.

Their test accuracy: 94%. Good enough to ship.

Their production accuracy: 70%.

That’s the talk I keep thinking about from AI Dev 26. The drop itself isn’t news. What they did about it is.

The accuracy gap has a cause

The 94% looked clean because the test set was curated. It covered the cases the team had thought of. Production didn’t care about that. It sent typos. Impossible numbers. Screenshots. Hand-drawn notes. Vague references with no context. Conflicting instructions from two people in the same email thread.

The test distribution and the production distribution weren’t the same. They almost never are.

A better model didn’t close the gap. They ran shadow testing: the agent processed real production emails alongside their human team for four weeks, generating payroll entries but not submitting them. Humans reviewed the shadow outputs. Edge cases surfaced. New tests got written.

Final accuracy: 98%. The agent didn’t change. The scaffolding around it did.

Month Accuracy
M1 55%
M2 97%
M3 87%
M4 94%
M5 (live) 70%
M6 (shadow) 98%

Six months of accuracy data from the payroll agent. The dip at M5 is what shipping without production-distribution testing looks like.

The 20/80 problem

The final slide from that talk had a number I wrote down immediately: agent engine = 20% of the work. The durable system around the agent = 80%.

That ratio feels off if you’ve spent most of your time thinking about which model to use, how to prompt it, how to evaluate it against a benchmark. Those things matter. They’re just not where a production AI project lives or dies.

The 80% is the multi-stage evaluation pipeline. Shadow testing infrastructure. The control tower that gives ops and leadership visibility into what the agent is actually doing. Input governance for the weird formats production throws at you. The routing logic that decides which step of the workflow a given input actually belongs in.

None of that is prompt engineering. All of it is platform work.

I’ve spent 30 years watching organizations adopt new technology and invest heavily in the visible capability while underbuilding the infrastructure that makes it last. The pattern is consistent. AI isn’t running a different play.

What breaks without the infrastructure

Enterprise AI conversations split fast once you get past the demo stage. Some teams want to know about governance, evaluation pipelines, how outputs get reviewed before they do anything irreversible. Most are asking which model to use and when they can ship.

The 70% drop happens. Without a control tower to surface it, teams find out through complaints, not metrics.

That’s a platform problem. Someone has to own the pipeline, not just the agent.

The line I can’t stop thinking about

Day two had a closing panel. Loose, riffing. One panelist dropped a line that’s been with me since:

“If you don’t own your harness, you don’t own your memory.”

It took a beat to unpack. Your harness is your evaluation infrastructure: the test pipelines, the shadow mode, the tooling that decides what “good” looks like for your specific agents on your specific workloads. Your memory is what that harness teaches you over time: where your agents fail, which prompts hold up under real traffic, what your actual production distribution looks like.

Outsource the harness to a vendor and the vendor runs your evaluation loop. They see your production failures first. Every edge case your agents surface builds their system’s understanding, not yours.

Most teams are focused on which LLM provider to pick, which coding assistant to standardize on. The harness question comes later, usually when a vendor relationship turns complicated and they realize how hard it is to move.

The payroll team built their own. Multi-stage evals, shadow infrastructure, control tower, four weeks of real production traffic before anything touched the write path. That’s why they landed at 98%. And that’s why the knowledge of how to get there belongs to them.

Twenty percent for the agent. Eighty percent for the system around it. Teams that understand that ratio are the ones shipping agents that stick.

I Stayed Up Until 3 AM to Build a Better Claude Code Guide Than the One With 52,000 Stars — Here’s What I Found

One night. One obsession. One repo that changed how I think about AI-assisted engineering.

It started the way most dangerous ideas do — quietly, at night, when the world had gone to sleep and the only sound was the hum of my monitors and the distant pulse of a city that never really stops.

I was tired. Not the kind of tired that sleep fixes. The kind that builds up when you’ve been consuming other people’s work for so long that you start to forget you’re capable of creating something yourself. I’d been scrolling GitHub for weeks — studying repositories, reading READMEs, bookmarking things I told myself I’d “come back to.” I was good at bookmarking. Not so good at building.

Then I found it.

A GitHub repository about Claude Code best practices. 52,800 stars. 5,300 forks. Trending number one globally. The kind of numbers that make you feel small just looking at them. I read through it — and it was good. Really good. Community-built, battle-tested, full of real workflows from real teams. The kind of thing that takes months and dozens of contributors to produce.

I closed my laptop.

Then I opened it again.

Because something wouldn’t let me go. Not jealousy — something quieter than that. A question. What would happen if I tried to understand this so deeply that I could build something better? Not to compete. Not to steal their stars. Just to find out if I had it in me.

It was 11 PM. I made coffee I didn’t need. I pulled up the repository again, opened a blank terminal, and typed git clone https://github.com/vignesh2027/claude-best-practice.git into an empty folder. The cursor blinked at me. Waiting.

I started writing.

I want to be honest about what those hours felt like — because most people only share the highlight reel. The clean commits. The polished README. The final product. They don’t talk about the 1 AM moment when you’ve been writing for two hours and you’re not sure if what you’re making is actually good or if you’re just too tired to tell the difference. They don’t talk about reading the original repo again at 1:30 AM, feeling like maybe you should just close the laptop and accept that 52,000 people already found what they needed.

I kept going anyway.

I wrote about context management — the thing nobody explains properly, the thing that silently kills your Claude Code sessions while you wonder why the output is getting worse. I wrote about verification loops — Boris Cherny’s single most impactful insight, that running tests automatically after every edit improves output quality by 2–3x. I wrote about plan mode and why skipping it is like starting a road trip without checking the map. I documented hooks with real, copy-paste-ready shell scripts. I built out 9 skill types from Thariq at Anthropic — a framework so clean it made me angry that I’d never seen it laid out this clearly before.

I studied how Superpowers ships with Claude Code. How BMAD turns a vague idea into a production feature through a structured sequence of phases. How the gstack team runs a 14-stage process where every stage is a command, not a meeting. How the creator of Claude Code himself runs 5 local sessions and 5 cloud sessions simultaneously, codes primarily by voice, and says the single best thing you can do is give Claude a way to verify its own work.

By 2 AM I had written more than I’d written in the previous month combined.

By 2:47 AM — the clock you can see in the cover image, the real clock, the one I photographed because I wanted to remember this night — I had something I was genuinely proud of. Not because it was perfect. Because it was mine. Built from obsession, not obligation. From curiosity, not a content calendar.

Here is what I learned that night — and I don’t mean about Claude Code.

I learned that the gap between consuming and creating is smaller than you think, and larger than it feels. It’s smaller because you already know more than you give yourself credit for. It’s larger because actually sitting down and making something — not planning to make it, not bookmarking it, not talking about making it — requires a different kind of commitment than most people are willing to give at 11 PM on a Tuesday.

I learned that the 52,000-star repo isn’t your competition — your own hesitation is. The community that built that repository didn’t sit down one night and produce 52,000 stars. They started somewhere. With something. Probably something smaller and rougher than what I built in one session.

I learned that depth beats breadth every time. The original repo has volume — hundreds of community contributions, tips from dozens of engineers. What I built has something different: a thread. A single person’s attempt to understand something completely, from first principles, all the way through. That’s a different kind of value. Not better or worse — different. And different is worth making.

I learned that the work you do at 2:47 AM, when nobody is watching and nothing is guaranteed, is the most honest work you’ll ever do. It’s not for likes. It’s not for followers. It’s for the version of yourself that gets up the next morning and looks in the mirror and knows: I actually did it.

The repository has everything I wish existed when I started learning Claude Code seriously:

10 deep-dive best practice guides — context management, hooks, subagents, MCP servers, CLAUDE.md mastery, prompting, model selection
5 advanced pattern guides — multi-agent teams, cross-model routing, automated pipelines, security hardening, enterprise deployment
Ready-to-use hook scripts — copy, paste, and run. Verification loops, auto-formatting, secrets protection, audit logging
Boris Cherny’s creator workflow — how the actual creator of Claude Code uses his own tool, in detail
Thariq’s 9 skill types framework — the most systematic approach to building Claude Code skills I’ve ever seen
Real-world team patterns — Superpowers, BMAD, gstack 14-stage, Spec Kit, Debugging War Room
70 tips from the creators themselves — distilled, organized, with a top-10 synthesis
Batch migration scripts — parallelize 100-file migrations across worktrees
PR babysitter automation — /loop 5m /babysit-prs handles your PR queue while you sleep
It is, genuinely, the most comprehensive Claude Code resource I know of. I say that not to brag — I say it because I spent a full night making sure it was true.

The city is still glowing outside my window.

The clock has moved past 3 AM.

My coffee is cold and I haven’t noticed until right now.

And I have something I didn’t have yesterday — a repository that represents the outer edge of what I understand, pushed to its limit, built past the point where most people stop.

If you’re reading this and you’ve been bookmarking things without building them — I see you. I was you. The only thing that changed was one decision, one night, one refusal to close the laptop.

Dream it. Build it. Break limits.

⭐ The repository is live. Everything is free. No newsletter. No paywall. Just the work.

Github 🙁https://github.com/vignesh2027/claude-best-practice.git).

If this helped you, a star means more than you know. And if you build something with it — tell me. That’s the only reward that matters.