Shopify Functions vs Shopify Scripts: A Migration Walkthrough

We covered the Scripts deprecation in an earlier post. The short version: Shopify Scripts stop running on June 30, 2026, and Shopify Functions are the replacement. As of April 15, 2026, you can no longer edit or publish new Scripts, which means Plus merchants who haven’t migrated yet are now in the danger zone.

This post is the next layer down. It’s the walkthrough. What the actual migration looks like in code, what breaks in translation, and the order we run the work in when a client hands us a Script Editor full of Ruby and asks us to make it Functions.

If you have a developer on your team, this gives them a sane starting point. If you don’t, it gives you a clear picture of what you’re hiring for.

The Mental Model Shift

Scripts and Functions solve the same problem in very different ways. The biggest mistake we see is people trying to port Scripts line-by-line. That doesn’t work, because the underlying contract is different.

Scripts: Ruby code that runs inside Shopify’s checkout, mutates a Cart object directly, and returns the mutated cart. You read properties, you write properties, you’re done.

Functions: A WebAssembly module that receives a GraphQL input, returns a list of operations (FunctionRunResult), and lets Shopify apply those operations. You don’t mutate state. You declare what should change.

If you internalize that one shift, the rest of the migration is mechanical.

The Migration Order We Run

We don’t write a single line of Function code until steps 1 through 3 are done. Skipping ahead is how migrations slip three weeks past the deadline.

  1. Audit the Scripts. Read every active Script in Script Editor. Write down what each one does in plain English, including edge cases. Half the time, a Script does something the merchant forgot was even live.
  2. Map to native features. Shopify’s native discount and checkout customization features have grown a lot since 2016. Several Scripts we audit each year don’t need to become Functions. They can become native Automatic Discounts or Discount Combinations.
  3. Group what’s left. The leftover Scripts get grouped by extension point. Discount logic goes to a Discount Function. Shipping logic goes to a Delivery Customization Function. Payment logic goes to a Payment Customization Function.
  4. Pick the toolchain. We build Functions on Gadget.dev because it cuts the boilerplate dramatically, but vanilla Shopify CLI plus a Rust or JS template works fine for a one-off.
  5. Translate, deploy, test in a development store, then promote. Never test Function changes against the live checkout. Use a dev store with the same product catalog shape.

A Real Translation: Tiered Discount

Here’s the canonical “buy more, save more” Script that almost every Plus merchant has some version of.

Before (Ruby Script):

class TieredDiscount
  def run(cart)
    quantity = cart.line_items.reduce(0) { |sum, item| sum + item.quantity }

    discount_percent = case quantity
      when 0..2 then 0
      when 3..5 then 10
      when 6..9 then 15
      else 20
    end

    return if discount_percent == 0

    cart.line_items.each do |line_item|
      line_item.change_line_price(
        line_item.line_price * (Decimal.new(100 - discount_percent) / 100),
        message: "Tier discount (#{discount_percent}% off)"
      )
    end
  end
end

TieredDiscount.new.run(Input.cart)
Output.cart = Input.cart

After (Shopify Function in JavaScript):

// src/run.js
export function run(input) {
  const totalQuantity = input.cart.lines.reduce(
    (sum, line) => sum + line.quantity,
    0
  );

  const discountPercent =
    totalQuantity >= 10 ? 20 :
    totalQuantity >= 6  ? 15 :
    totalQuantity >= 3  ? 10 : 0;

  if (discountPercent === 0) {
    return { discounts: [], discountApplicationStrategy: "FIRST" };
  }

  return {
    discounts: [
      {
        message: `Tier discount (${discountPercent}% off)`,
        targets: input.cart.lines.map((line) => ({
          productVariant: { id: line.merchandise.id, quantity: line.quantity }
        })),
        value: { percentage: { value: discountPercent.toString() } }
      }
    ],
    discountApplicationStrategy: "FIRST"
  };
}

A few things to notice in the diff:

  • No mutation. We return a discounts array, we don’t reach into the cart and rewrite line prices.
  • Targets are explicit. Scripts let you call change_line_price on each line. Functions ask you to declare which variants the discount applies to and how it stacks via discountApplicationStrategy.
  • The data shape is GraphQL. cart.line_items is now cart.lines, line_item.quantity is now line.quantity, and you’ll spend the first hour of every migration relearning these names.

The GraphQL Input File Is Where People Get Stuck

Functions don’t receive the entire cart. They receive whatever you ask for in an input.graphql query. This is the file that surprises every developer the first time.

# src/input.graphql
query Input {
  cart {
    lines {
      quantity
      merchandise {
        ... on ProductVariant {
          id
          product {
            id
            tags
          }
        }
      }
    }
  }
}

If you forget to query tags here, you cannot read tags inside run.js, no matter how many console.log calls you add. The first hour of debugging a misbehaving Function is almost always “the input query is missing a field.”

Shipping and Payment Customization

Scripts had a single execution point: checkout. Functions split that into separate APIs:

  • Delivery Customization Function for shipping. Hide options, rename them, reorder them.
  • Payment Customization Function for payment methods. Same operations.
  • Cart Transform Function if you need to expand a single line into multiple lines (bundles, components).

A Script that hid Express Shipping for heavy carts becomes a Delivery Customization Function that returns a hide operation:

export function run(input) {
  const totalGrams = input.cart.lines.reduce(
    (sum, line) => sum + (line.merchandise.weight ?? 0) * line.quantity,
    0
  );

  if (totalGrams < 20000) {
    return { operations: [] };
  }

  const expressOption = input.cart.deliveryGroups
    .flatMap((group) => group.deliveryOptions)
    .find((option) => option.title === "Express");

  if (!expressOption) {
    return { operations: [] };
  }

  return {
    operations: [
      { hide: { deliveryOptionHandle: expressOption.handle } }
    ]
  };
}

The pattern is consistent across all three customization APIs: query what you need, decide what should change, return operations.

Gotchas We Hit Often

These are the ones that have actually cost us hours, not the ones we read about.

The instruction-count limit is real. Functions enforce a strict execution budget measured in instructions (around 11 million), not wall-clock time, along with a 125KB input size cap. Loops over the entire catalog, regex on every line item description, and deeply nested .find() chains will blow past it on a busy cart. Profile early.

undefined vs missing fields. GraphQL only returns what you queried. If a field isn’t in input.graphql, it’s not null in your input object, it’s not present at all. Defensive code matters.

No external HTTP calls. Scripts didn’t allow them either, but people sometimes assume Functions do because Functions are deployed as part of an app. They don’t. If you need data from outside Shopify, fetch it ahead of time and pass it through metafields.

Discount stacking. Scripts let you stack discounts implicitly. Functions force you to declare a discountApplicationStrategy of FIRST or MAXIMUM. Pick the wrong one and your customers see different totals than they did under Scripts.

Localization and currency. Scripts gave you the cart’s currency on the cart object. In Functions, you query it via cart.cost.totalAmount.currencyCode (or similar paths depending on the API). Hardcoding CAD because the dev store is in CAD is the kind of bug that ships to a US store and breaks Black Friday.

Testing tools are different. Scripts had a console in the admin. Functions are tested with shopify app function run against fixture inputs. Build out a small set of fixture carts that represent your real edge cases, and run them on every change.

How Long Does a Migration Take?

For a single straightforward Script (one rule, one extension point), expect a day of work end to end. Audit, translate, test, deploy.

For a Plus merchant with eight to twelve active Scripts spread across discounts, shipping, and payment, expect two to three weeks. Most of that time is in steps 1, 2, and 5. The actual coding is the smallest part.

If your store has Scripts you’ve forgotten about, has Scripts written by someone who left the company, or has Scripts that interact with each other in ways nobody documented, double the estimate. We’ve seen migrations that took six weeks because the audit kept turning up new edge cases.

How We Run Scripts to Functions Migrations

When clients bring us this work, our process is:

  1. Read-only audit week. We get Script Editor access, document everything, and produce a migration plan with native-feature swaps called out.
  2. Build week(s). Functions written, deployed to a dev store, tested against fixture carts that match the merchant’s real traffic.
  3. Cutover. Functions enabled in production, Scripts disabled the same day. We watch checkout closely for 48 hours.
  4. Post-cutover review. We document what changed and hand the codebase off, since Functions live in a real Git repo, not in an admin panel.

If you’re sitting on Scripts and the June 30 deadline is starting to feel close, get in touch. We can run the audit week and tell you exactly how big the project is before you commit to a build. For more on how we build Functions and apps quickly, see why we build Shopify apps on Gadget.dev and our Shopify app development service.

The Bottom Line

The migration isn’t conceptually hard. It’s a translation job between two systems that solve the same problem. The discipline is in the audit and the testing, not the coding. Start with what you have, map what’s native, and write Functions only for what’s actually left.

The merchants who started this work in 2025 are done. The ones who started in early 2026 are mid-project. The ones who haven’t started yet still have time, but the runway is roughly eight weeks and shrinking.

Rethinking Geo-Blocking and Stripe’s Failures in Global Access: A Cautionary Tale of Misoptimization

The Problem We Were Actually Solving

Our business relies on integrations with popular payment gateways, such as Stripe. However, as we expanded our reach to countries like Nigeria, Pakistan, Ghana, and Bangladesh, we encountered a glaring issue: geo-blocking. These platforms, while convenient for users in developed economies, effectively shut out creators from the rest of the world. When our team first identified this problem, we assumed it was a matter of tweaking API endpoints or implementing region-specific routing. Little did we know that the real challenge lay beneath the surface.

What We Tried First (And Why It Failed)

Initially, we attempted to sidestep the issue by using Stripe’s geo-restricted APIs, hoping to work around the blockage. However, this approach led to a multitude of problems. Transactions would fail, and error messages would surface only after the payment processing pipeline had been initiated. This resulted in frustrated creators and a loss of revenue. We soon realized that geo-blocking was not just a matter of restricting API access – it was a systemic problem that permeated the entire payment processing pipeline.

The Architecture Decision

After weeks of research and experimentation, our team arrived at a groundbreaking realization: we didn’t need to compromise on global access by working within the existing limitations of Stripe’s system. Instead, we decided to create our own payment gateway, one that would bypass the restrictions imposed by third-party providers. By doing so, we would not only ensure seamless transactions worldwide but also provide a more robust, customizable payment processing system for our creators.

What The Numbers Said After

The decision to build our own payment gateway proved to be a resounding success. We saw an average reduction of 30% in transaction failures, with a corresponding increase in revenue for our creators. Moreover, the latency on our payment processing pipeline dropped by 15%, resulting in a significant improvement in user experience. These numbers not only validated our architecture decision but also underscored the importance of building a system tailored to our specific needs.

What I Would Do Differently

In hindsight, I would advise my team to explore the use of alternative payment gateways that prioritize global accessibility from the outset. While building our own system was a necessary step, it was also a costly and time-consuming endeavor. By leveraging established platforms with a proven track record of international support, we might have avoided the complexities and setbacks we encountered. Nonetheless, the lessons learned from this experience have been invaluable, serving as a testament to the importance of adaptability and creativity in the face of seemingly insurmountable challenges. In the world of systems engineering, it’s not always about choosing the most straightforward solution – it’s about being willing to challenge the status quo and push the boundaries of what’s possible.

I Built a Free Brat Generator – Here’s What I Learned About Next.js Performance published

The brat aesthetic is simple by design — bold lowercase text, solid color background, nothing else. Building a tool around that simplicity turned out to be more interesting than I expected.
I built ibratgenerator.com — a free brat-style image generator inspired by Charli XCX’s album aesthetic. Here is what the build taught me about Next.js performance, canvas rendering, and SEO.
What the Tool Does
Users open the page, type any text, pick a background color, and download a high-resolution PNG. No signup, no watermark, no account needed. The core stack is Next.js 16 App Router with a canvas-based rendering engine written in vanilla TypeScript.
The tool supports:

  • Custom background and text colors
  • Aspect ratio presets (1:1, 4:5, 9:16, 16:9)
  • Stickers and emoji overlays
  • Typography controls — font size, letter spacing, alignment
  • Export up to 3000px PNG
  • Full mobile touch support

The Next.js Dynamic Import Problem
The canvas component is entirely client-side — it uses browser APIs that do not exist on the server. So I loaded it with next/dynamic and ssr: false:

tsxconst BratGeneratorLazy = dynamic(
  () => import('./BratGenerator'),
  { ssr: false }
)

This works fine on mobile. But on desktop, Google was crawling the page and seeing a large blank space where the tool should be. The LCP element had no reserved space, so the page layout shifted when the component loaded.

The fix was a simple wrapper:

tsx<div style={{ 
  minHeight: '520px', 
  position: 'relative', 
  width: '100%' 
}}>
  <BratGeneratorLazy />
</div>

That single change improved desktop position significantly in Google Search Console. The position: relative and width: 100% matter — without them the stacking context breaks and the component can overlap the sticky header on scroll.
Canvas Performance — History Snapshots
The tool has undo/redo support. Every user interaction pushes a state snapshot to a history array. The original implementation was cloning the background image on every snapshot:

typescript// Before — wrong
bgImage: s.bgImage
  ? (() => {
      const img = new Image();
      img.src = s.bgImage!.src;
      return img;
    })()
  : null,

Creating a new HTMLImageElement on every keystroke causes heap churn and UI thread stutters — especially noticeable on mobile. The fix is direct reference assignment:

typescript// After — correct
bgImage: s.bgImage,

The image object is static between snapshots. Copying the reference is safe and eliminates the unnecessary DOM instantiation on every interaction.
Pointer Event Cleanup
Global pointer listeners were added to window for sticker drag handling:

typescriptwindow.addEventListener("pointermove", onPointerMove)
window.addEventListener("pointerup", onPointerUp)
window.addEventListener("pointercancel", onPointerUp)

In React StrictMode, components mount twice in development. Without explicit cleanup before adding listeners, you get duplicate event handlers that cause double-trigger bugs on drag release. The fix:
typescript// Remove before adding to prevent duplicates

window.removeEventListener("pointermove", onPointerMove)
window.removeEventListener("pointerup", onPointerUp)
window.removeEventListener("pointercancel", onPointerUp)

window.addEventListener("pointermove", onPointerMove)
window.addEventListener("pointerup", onPointerUp)
window.addEventListener("pointercancel", onPointerUp)

Content Security Policy with Next.js
Adding CSP headers in next.config.ts broke Microsoft Clarity because the script loads from scripts.clarity.ms but sends data to t.clarity.ms — two different subdomains that both need to be allowlisted:

typescript// script-src needs scripts.clarity.ms
// connect-src needs t.clarity.ms
// Both subdomains required — just clarity.ms is not enough

The lesson: always check the actual network requests in DevTools after adding CSP headers. The error messages in the console tell you exactly which domain is being blocked.
What I Would Do Differently
The multilingual routing I added early on (/[lang]/ dynamic segment) caused a TypeScript build error after I removed the routes but forgot to clear the .next cache. The stale types in .next/dev/types/validator.ts kept referencing the deleted route.
The fix was deleting the .next directory entirely and running a fresh build. Simple, but it cost debugging time. Clear your cache when you make structural routing changes.
The Tool Is Live
Brat Generator — free, no signup, watermark-free PNG export. Works on mobile and desktop.
If you are building canvas-based tools in Next.js, the dynamic import + reserved space pattern is worth keeping in your toolkit. The LCP improvement from that single wrapper div was more significant than I expected.

Built with Next.js 16, TypeScript, and vanilla Canvas API. Deployed on Vercel.

Found a Second Layer to a GitHub Follow Botnet?

This is Parts 2 of an ongoing investigation. Part 1 documented the initial discovery — 8 accounts with Jaccard following-list similarity of 0.99+ across ~29,800 entries each, evading cross-follow detection entirely.

After Part 1 published, I kept pulling the data.

Subsequent analysis expanded the cluster to 9 accounts, recovered infrastructure linkage to a specific GitHub identity, and mapped the generation pipeline responsible for all 552 repositories across the cluster. The pipeline left recoverable artifacts in every repository it produced.

Following that same pipeline fingerprint led to an earlier operation — running nine months before the follow botnet was provisioned. The same GitHub identity appears in both. So does the same generator artifact. Four accounts documented in Part 1 appear in both operations.

This post documents what the data shows. Inference is labeled as inference throughout. I am not establishing intent or ownership beyond what the API evidence directly supports.

The Cluster Expanded

Running Jaccard similarity analysis against the original 8 accounts and their extended follower graphs surfaced a ninth account: lynewinter.

Pairwise Jaccard similarity against mariwatts:

lynewinter  ↔  mariwatts   jaccard=0.9898   shared≈29,200

The methodology is identical to Part 1. A coefficient of 0.9898 across ~29,800 following entries places this pair within the same anomalous range as the original cluster. The same alternative explanations from Part 1 apply and fail at the same scale.

The confirmed cluster is now 9 accounts:

canestein, hazexone, domcomit, kylehyne, jaderytm,
vierystein, hanyvert, mariwatts, lynewinter

552 Repositories, One Embedded Timestamp per Account, 34-Minute Span

Each of the 9 accounts has between 57 and 63 public repositories. Total across the cluster: 552 repositories.

Every repository was created on May 12, 2026. Fetching the first repository per account and reading the raw README returned an HTML comment — invisible on the rendered page — containing a creation timestamp and a job identifier:

2026-05-12 11:10:39 | hanyvert   | SwapLink     | job=48099
2026-05-12 11:18:46 | jaderytm   | GasSync      | job=39412
2026-05-12 11:27:52 | hazexone   | BitForge     | job=63871
2026-05-12 11:30:00 | canestein  | BlockLink    | job=51606
2026-05-12 11:33:07 | mariwatts  | MintChain    | job=82564
2026-05-12 11:35:37 | vierystein | HashSync     | job=20845
2026-05-12 11:38:58 | kylehyne   | SmartLink    | job=38575
2026-05-12 11:42:07 | lynewinter | YieldChain   | job=78012
2026-05-12 11:44:30 | domcomit   | ProjectCloud | job=26977

The first and last timestamps are 34 minutes apart. The job IDs are non-sequential across accounts. Sequential job dispatch from a single process would produce monotonically increasing IDs; non-sequential IDs are consistent with a job queue dispatching work across multiple workers concurrently. That is one interpretation — the data does not rule out other scheduling patterns.

The comment format is consistent across all sampled READMEs:

<!-- fallback_BlockLink_20260512113000_51606 -->

The fallback_ prefix is present in every instance retrieved. In the context of template generation systems, a fallback_ label typically indicates the primary generation path failed and a static secondary template was substituted. Whether that interpretation applies here is inference — what is directly observable is that the prefix is consistent across all 552 repositories and across both the 2026 and 2025 operations documented below.

Repository Contents

Fetching file trees and raw content from sampled repositories across all 9 accounts returned the same structural pattern.

A representative Python file (blocklink.py, 1,656 bytes):

class BlockLink:
    def run(self) -> bool:
        try:
            self.logger.info("Starting BlockLink processing")
            # Add your main logic here
            self.logger.info("Processing completed successfully")
            return True

The comment # Add your main logic here is the sole content of the method body. Every sampled repository follows this pattern: a class stub, a logging initializer, an argparse entry point, and a test file that instantiates the stub. No functional implementation was found in any sampled file.

Repo names follow a [Word][Suffix] pattern. Suffixes are drawn from a fixed set: Core, Chain, Sync, Vault, Forge, Link. Descriptions are consistent with AI-generated output.

Across all 552 repositories:

  • Stars: 0
  • Forks: 0
  • PyPI uploads: none found
  • CI/CD configuration files: none found (no .github/workflows/, no .travis.yml)
  • Open issues: 0
  • Pull requests: 0

These absences are observable facts. Their interpretation — what they indicate about the repositories’ purpose — is addressed in the inference section below.

A Template Substitution Error Confirms Shared Generation

The LICENSE section of every generated README contains a hardcoded URL. That URL uses mariwatts as the repository owner regardless of which account’s repository it appears in.

From canestein/BlockLink:

See the LICENSE file at https://github.com/mariwatts/BlockLink/blob/main/LICENSE

From lynewinter/YieldChain:

See the LICENSE file at https://github.com/mariwatts/YieldChain/blob/main/LICENSE

The repo name variable was substituted correctly. The account name variable in the LICENSE URL field was not. mariwatts appears to be the base account in the generation template — the value that was present when the template was authored and not replaced during per-account substitution.

This pattern was confirmed across multiple accounts and multiple repositories. It is not present in the mariwatts repositories themselves, where the URL resolves correctly.

The Pipeline Is Linked to a Specific GitHub Identity via Commit Metadata

Every repository across all 9 accounts contains a co-author trailer in its commit history:

Co-authored-by: Hajigur <[email protected]>

GitHub’s authenticated noreply format is [email protected]. The numeric ID prefix is assigned by GitHub at account creation and embedded by GitHub’s own systems when a commit is pushed through an authenticated session. It is not user-configurable and cannot be produced by manually typing a username.

The GitHub account hajigur69 has the internal numeric ID 66867581. This can be verified:

curl -s https://api.github.com/users/hajigur69 | python3 -c 
  "import json,sys; u=json.load(sys.stdin); print(u['id'])"
# 66867581

Fetching the commit history of hajigur69‘s own repository (Cloud9, created February 2026) returns commits authored with the same identifier:

Author: Hajigur | [email protected]

The same authenticated GitHub ID (66867581) appears in commits across all 9 cluster accounts and in commits authored directly by hajigur69. GitHub account created June 13, 2020. At time of retrieval: 903 followers, 679 following. Public bio field: lamer.

This co-author line is present in every repository across the cluster. It links the cluster’s commit history to a specific, authenticated GitHub identity. It does not establish that hajigur69 provisioned the accounts or controls them — it establishes that the same authenticated identity was used to produce commits in the cluster’s repositories and in hajigur69‘s own repository.

Infrastructure: carox.tech

Two cluster accounts — canestein and lynewinter — use a custom email domain in their git commit author metadata:

canestein  → [email protected]
lynewinter → [email protected]

These addresses appear in the author field of every commit on those accounts. Git records the author email at commit time from the local git configuration of the machine that made the commit.

WHOIS and DNS records for carox.tech:

Creation Date:  2025-07-19
Updated Date:   2025-08-01
Registrar:      Namify Domains Inc
Name Servers:   raphaela.ns.cloudflare.com
                uriah.ns.cloudflare.com

DNS:
  A record:  none
  MX:        route1.mx.cloudflare.net (priority 44)
             route2.mx.cloudflare.net (priority 84)
             route3.mx.cloudflare.net (priority 28)
  TXT:       v=spf1 include:_spf.mx.cloudflare.net ~all

The domain has no A record and no web presence. The MX records point to Cloudflare Email Routing, a free service that forwards inbound email from a custom domain to an external inbox. The destination inbox is not publicly recoverable from DNS.

The domain was created July 19, 2025 and updated August 1, 2025 — approximately 10 months before the cluster’s repository generation run on May 12, 2026. It predates the provisioning event.

A Second Malformed Co-Author Address

In addition to the hajigur69 co-author trailer, a second co-author line appears on commits across 8 of the 9 cluster accounts:

Co-authored-by: v <[email protected]>

There is a GitHub account with login v, internal ID 627846. Its correctly formatted noreply address is [email protected]. The string in these commits — [email protected] — is missing the numeric ID prefix that GitHub generates automatically during an authenticated push. It cannot be produced by a normal authenticated session.

The most likely explanations: a user.email set manually in a local git config, a placeholder from a development environment not replaced before deployment, or a test identity carried into production unchanged. All three produce the same result: consistent across 8 of 9 accounts, indicating it was set once and not audited.

This is not an attribution of the v GitHub account to this operation. That account’s correctly formatted address is not present in these commits.

What the Repository Evidence Does and Does Not Show

What the data shows directly:

  • 552 repositories were created across 9 accounts in a 34-minute window on a single day
  • Every repository contains a consistent embedded comment format with a fallback_ prefix, generation timestamp, and non-sequential job ID
  • No repository in the cluster contains functional implementation code in any sampled file
  • No repository has stars, forks, CI configuration, issues, or pull requests
  • All repositories were generated from a shared template, evidenced by the LICENSE URL substitution error
  • The same authenticated GitHub identity (66867581+hajigur69) is present in commit metadata across the cluster and in that identity’s own repository

What the data does not show directly:

  • Who controls the 9 cluster accounts
  • What the repositories or the follow botnet are used for after provisioning
  • Whether hajigur69 is the operator, a collaborator, or an identity whose credentials were used without their knowledge
  • Whether the absence of implementation code indicates any specific intended use

The downstream use of this infrastructure is an open question. The confirmed finding is the shared generation pipeline, the common commit identity, and the behavioral coordination documented in Part 1.

An Earlier Operation: The Same Fingerprints, Nine Months Prior

The 66867581+hajigur69 co-author string and the fallback_ generator artifact do not appear for the first time in May 2026.

GitHub’s commit search API returns the same string across thousands of commits from a cluster of 22 accounts in a July–August 2025 window:

2025-07-08..2025-07-14  →  1,738 hits
2025-07-15..2025-07-21  →    701 hits
2025-07-22..2025-07-31  →    949 hits
2025-08-01..2025-08-15  →  7,194 hits
2025-08-16..2025-08-31  →      0 hits

Four accounts from the 2026 follow botnet cluster — canestein, hazexone, domcomit, kylehyne — are present in this earlier commit set. The activity stops completely on August 16, 2025. The cause of that stop is not established by this data.

Lyne6666: Account Creation and Repository Generation

Lyne6666 is a GitHub account created May 3, 2025 with 163 public repositories. The GitHub API returns a creation timestamp of July 9, 2025, 18:55 UTC for all 163 repositories. Multiple commits across these repositories share the same minute-level timestamp.

The LICENSE file SHA across all 163 repositories:

8aa26455d23acf904be3ed9dfb3a3efe3e49245a

Git computes hashes from file content. An identical SHA across 163 repositories means 163 identical LICENSE files. A single source file was copied into every repository without modification.

Repository names follow the pattern {Tech}{Testnet}{Function}{Suffix}:

TPUTestnetSystemSolutionsNext
DAOTestnetAIKitPro
IoTTestnetPlatformLabsX

The install section of every README contains:

pip install git+https://github.com/Lyne6666/{RepoName}.git

This instruction is present across all 163 repositories. No postinstall hook content was confirmed in the repositories examined. The presence of the install vectors is a direct observation; what a developer who followed these instructions would receive is not established by this analysis.

uhsr: Commit Volume and Account Metadata

The Lyne6666 repositories contain [email protected] in their commit author email fields. eteb.me is a private domain with WHOIS privacy protection via Identity Digital. The account uhsr was created July 10, 2025 — one day after Lyne6666‘s mass repository creation timestamp.

At time of retrieval: 237 public repositories, 2,972 followers, 30,778 following.

GitHub API commit volume for uhsr by month:

July 2025:      1,382 commits
August 2025:      247 commits
September 2025:    21 commits
October 2025:      96 commits

July 2025 accounts for 71% of uhsr‘s all-time commit count at the time of retrieval.

The Backdated Commit History

uhsr/AssetMarket contains a .Logs file with approximately 365 entries spanning January 1, 2025 through December 31, 2025. Each entry follows the format: Logs: YYYY-MM-DD <8charToken>.

The repository creation date, returned by the GitHub API:

curl -s "https://api.github.com/repos/uhsr/AssetMarket" | python3 -c 
  "import json,sys; r=json.load(sys.stdin); print(r['created_at'])"
# 2025-08-02T16:29:22Z

The root commit, retrieved by paginating the commit history to its earliest entry:

SHA:            4f8f47697eb89c8818820ca92348be01c4544878
Message:        Logs on 2025-01-01
Author date:    2025-01-01T14:47:47Z
Committer date: 2025-01-01T14:47:47Z
Author email:   [email protected]

The repository did not exist until August 2, 2025. The root commit carries an author date of January 1, 2025 — 213 days earlier.

Git stores two timestamps per commit: GIT_AUTHOR_DATE and GIT_COMMITTER_DATE. Both are user-configurable before a push. When only the author date is overridden, the committer date retains the real push timestamp, producing a detectable mismatch. In the AssetMarket root commit, both fields are set identically to 2025-01-01T14:47:47Z. The mismatch that would normally expose backdating is not present.

The .Logs file content — daily entries with 8-character tokens across a full calendar year — is consistent with bulk generation rather than incremental accumulation, but that is an interpretation of the content pattern, not a directly provable fact.

The Generator Artifact in the 2025 Repositories

Fetching the raw README content of uhsr/AssetMarket returned this HTML comment at the top of the file:

<!-- fallback_AssetMarket_20250802163009_95172 -->

The same format as the 2026 cluster: fallback_ prefix, repo name variable, generation timestamp, and a numeric identifier.

The same comment was present in two additional repositories:

uhsr/SmartContract  →  <!-- fallback_SmartContract_20250802162757_83653 -->
uhsr/TokenLab       →  <!-- fallback_TokenLab_20250802161931_80263 -->

Three repositories, three comments, with timestamps spanning a 38-minute window on August 2, 2025:

16:19:31  TokenLab      → trailing ID: 80263
16:27:57  SmartContract → trailing ID: 83653
16:30:09  AssetMarket   → trailing ID: 95172

The trailing numeric identifiers increase across the three runs but not by a fixed increment — gaps of approximately 3,390 and 11,519 respectively. On Linux systems, process IDs are assigned sequentially; irregular gaps are consistent with other processes running between jobs consuming PID assignments. This is an interpretation of the pattern, not a definitive conclusion about the execution environment.

The fallback_ prefix is present in all three 2025 artifacts and in all 552 2026 repositories. The format is identical across both operations: fallback_{reponame}_{YYYYMMDDHHMMSS}_{numericID}.

The Stargazer Overlap

The following repositories had star counts in the range of 49–83 at time of analysis: AssetMarket (83 stars), SmartContract (50), DigitalWallet (49), BlockchainKit (36).

Pulling the full stargazer lists via the GitHub API and computing intersections:

AssetMarket ∩ DigitalWallet ∩ SmartContract = 33 accounts

33 accounts starred all three repositories — 67% of DigitalWallet‘s total star count from a single overlapping pool. For unrelated repositories with independent audiences, overlap at this concentration is not consistent with organic discovery; it would require the same users to independently find and star three separate, unpromotable repositories with no followers, no forks, and no search visibility.

The July 11, 2025 batch — 83 repositories created in a single day — showed the following star distribution:

★2:  64 repositories  (77%)
★1:  19 repositories  (23%)
★0:   0 repositories

Every repository in this batch received at least one star. The distribution has no zero-count entries and no values above 2 for this batch, producing a uniform two-tier pattern with no variance.

Two accounts from the 33-account overlap pool — SAPH1TE and ahnshy — also appear in the stargazer lists for Lyne6666 repositories. The uhsr cluster and the Lyne6666 cluster share no observable social graph overlap — different account creation dates, different repository naming patterns, no mutual followers or following relationships found. The two accounts appearing in both stargazer pools is the only cross-cluster link found in this data.

What produced the 33-account overlap and the two-tier star distribution is not established by this data. The overlap pattern is documented as an observable finding.

mohammadtzs

One fork of DigitalWallet exists: it was made by mohammadtzs. Account created March 2025, 506 public repositories, 100 forks. All 100 forks are from accounts that returned 404 at time of retrieval — the accounts have been deleted or suspended. Fork names included alork1, alork2, alork3, alorki1.

mohammadtzs also appears in the 33-account stargazer pool described above.

Observable facts: this account forked a uhsr cluster repository and is present in the shared stargazer pool. Account age, repository count, fork history, and the status of the forked-from accounts are all directly retrievable via the API.

October 2025: Repository Names

uhsr‘s commit activity dropped to 21 commits in September 2025 and returned to 96 in October. The October commits are concentrated in a single 15-minute window on October 20, 2025, between 05:04 and 05:19 UTC, across seven repositories:

awesomepythonTech
freeprogrammingbooksHub
publicapisAI
codinginterviewuniversityTools
developerroadmapLab
systemdesignprimerCloud
buildyourownxTools

The names append suffixes (Tech, Hub, AI, Tools, Lab, Cloud, X) to terms matching the names of widely-followed GitHub repositories:

  • vinta/awesome-python (290,000+ stars)
  • EbookFoundation/free-programming-books (340,000+ stars)
  • public-apis/public-apis (320,000+ stars)
  • jwasham/coding-interview-university (310,000+ stars)
  • kamranahmedse/developer-roadmap (300,000+ stars)
  • donnemartin/system-design-primer (280,000+ stars)
  • codecrafters-io/build-your-own-x (330,000+ stars)

None of the seven repositories contain implementation content. The description field of developerroadmapLab contains the string "enterprise enterprise-grade" — a duplicated token consistent with a template variable that was substituted twice or a template field whose value contained a placeholder that was not resolved before being written to the description.

Alternative Explanations

Before treating the connection between the 2025 and 2026 operations as confirmed attribution to a single operator, it is worth asking what else could produce these observations.

Could the hajigur69 co-author identity appear in unrelated operations by coincidence? The GitHub noreply format embeds a numeric account ID that is immutable and account-specific. The same numeric ID (66867581) appearing across thousands of commits in a 2025 commit-farming cluster and across all 552 repositories in the 2026 follow botnet cluster is not consistent with coincidence. The identity would have to be re-used deliberately or the same credentials would have to have been used in both operations.

Could the fallback_ artifact format be from a widely distributed open-source tool? Possible. If the fallback_ prefix is a convention from a publicly available README generation tool, its presence in both operations would indicate both operations used the same tool, not necessarily the same operator. No such tool was identified in the course of this research. The artifact format is not established as unique to a single actor.

Could the four accounts present in both the 2025 and 2026 operations be coincidentally shared? Four accounts from the 9-account 2026 follow botnet cluster — canestein, hazexone, domcomit, kylehyne — appear in the 2025 commit-farming activity set. These accounts were also identified in Part 1 as exhibiting Jaccard following similarity of 0.99+. Their presence across both operations is documented as a factual overlap. Whether that overlap reflects shared control is an inference.

I am not establishing that a single individual or organization controls both operations. I am documenting that the same authenticated GitHub identity, the same generator artifact prefix and format, and four of the same accounts appear across both.

Summary of Confirmed Findings

Finding Method
Cluster expanded to 9 accounts, all Jaccard ≥ 0.98 API-derived following list comparison
All 552 repos created May 12, 2026 in a 34-minute window Embedded HTML comment timestamps, GitHub API
<!-- fallback_NAME_TIMESTAMP_ID --> in every README Direct raw file fetch, all 9 accounts
mariwatts hardcoded in LICENSE URLs across all foreign accounts Direct raw file fetch, multiple accounts
66867581+hajigur69 co-author on all cluster commits Raw commit data, GitHub API
66867581+hajigur69 author on hajigur69‘s own repository Raw commit data, GitHub API
66867581 = hajigur69‘s GitHub numeric ID GET /users/hajigur69
[email protected] lacks numeric ID prefix Raw commit data, canestein/BlockLink
[email protected], [email protected] in commit author fields Raw commit data
carox.tech: no A record, Cloudflare MX only, created July 2025 WHOIS, DNS
All 552 repos: no stars, no forks, no CI, no issues, no PRs GitHub API
Same fallback_ format present in 2025 uhsr repositories Direct raw file fetch
uhsr/AssetMarket root commit dated 213 days before repo creation GET /repos/uhsr/AssetMarket/commits, GET /repos/uhsr/AssetMarket
Root commit SHA with both date fields set to 2025-01-01 4f8f47697eb89c8818820ca92348be01c4544878
PID artifacts in 3 README files, same machine, 38-minute window Direct raw file fetch, timestamp analysis
33-account pool appearing in all three high-value stargazer lists Stargazer API, cross-reference
SAPH1TE, ahnshy in both uhsr and Lyne6666 stargazer lists Stargazer API, cross-cluster comparison
canestein, hazexone, domcomit, kylehyne in both 2025 and 2026 operations Commit search API, Part 1 cluster data
October 2025 repos named after widely-starred GitHub repositories GitHub API, direct name comparison

Disclosure

This report has been submitted in full to GitHub Trust & Safety with API-verifiable evidence including the root backdated commit SHA (4f8f47697eb89c8818820ca92348be01c4544878), the generator artifact URLs, the 33-account stargazer overlap, and the complete account list.

All data was retrieved via the GitHub REST API v3 with authenticated requests. No accounts were accessed beyond their public API surface. No systems were compromised.

The accounts canestein, hazexone, domcomit, and kylehyne appear in both this report and Part 1. All account names published here are publicly visible GitHub profiles. This methodology is only verifiable if the data is reproducible.

If you have seen the hajigur69 co-author string or the fallback_ artifact pattern in your own repositories’ commit histories, that is the fingerprint documented here. Worth reporting.

All tooling used in this investigation is in BANANA_TREE.

AI Daily Digest: May 22, 2026 — Agentic Workflows, Coding Agents & Embodied AI

Cover

Cover

5-min read · Curated daily by an AI Systems Architect
Focus: Agentic Workflows · AI Coding Tools · Embodied Intelligence

1. Cursor 3.0 Unlocks “Agents Window” — Parallel AI Agents Across Git Worktrees

【Technical Core】
Cursor 3.0 (April 2026) retires the legacy Composer and introduces Agents Window — a full-screen workspace that runs multiple AI agents in parallel across local environments, isolated Git worktrees, SSH remotes, and cloud instances. Key additions: /worktree command for branch-isolated task sandboxing, Design Mode for browser-based UI annotation, /best-of-n for blind multi-model output comparison, and a JetBrains plugin — bringing agent orchestration to non-VS Code users.

【Why It Matters】
For the first time, an AI IDE treats agents as first-class workspace primitives rather than chat sidebar novelties. The ability to run agents in parallel across isolated Git worktrees solves the context-conflict problem that has plagued AI-assisted team development. This is the VS Code fork that decided it’s actually an agent coordination platform.

🔗 https://www.shareuhack.com/zh-TW/posts/cursor-vs-claude-code-vs-windsurf-2026

2. Claude Code Opus 4.7 Pushes SWE-bench Verified to 87.6%

【Technical Core】
Anthropic’s April 2026 update to Claude Code bundles the Opus 4.7 model, which achieves 87.6% on SWE-bench Verified (up from 80.8%) and 64.3% on SWE-bench Pro. Notable engineering: 1M-token context window (tool default still 200K), UI screenshot resolution bumped from 1.15MP to 3.75MP for visual code understanding, new xhigh effort tier between high and max, Task Budgets for token-constrained runs, and /ultrareview for deep code review reports. Background Agent and Auto Memories (persistent cross-session context) round out the agentic toolkit.

【Why It Matters】
87.6% on Verified is not a incremental gain — it crosses the threshold where autonomous code agents can meaningfully handle multi-file, multi-repo refactoring tasks that previously required human architects. Combined with 1M context and persistent memory, Claude Code is positioning itself as the autonomous layer between PM specs and production PRs.

🔗 https://vibecoding.app/blog/cursor-vs-windsurf

3. Windsurf 2.0 + Devin Cloud: Persistent Agents That Outlive Your Laptop

【Technical Core】
Following Cognition’s acquisition of Windsurf’s assets (July 2025), Windsurf 2.0 (April 2026) introduces Devin Cloud one-click offload: plan tasks locally in the Windsurf IDE, then dispatch execution to Devin’s cloud environment where agents continue running even after your local device shuts down. The Agent Command Center provides a Kanban-style dashboard for all running agents; Spaces package agent sessions, PRs, and context into portable task units with automatic context inheritance across sessions.

【Why It Matters】
The local-IDE-versus-cloud-agent dichotomy just collapsed. For long-horizon tasks (multi-module feature work, large-scale refactors), the ability to fire-and-forget to a persistent cloud agent while your laptop sleeps is a genuine workflow unlock. At $20/month Pro pricing with Devin-level autonomy, this is the budget-friendly entry into persistent agent workflows.

🔗 https://zeeklog.com/2026-aibian-cheng-gong-ju-agentshi-dai-zhong-ji-heng-ping-cursor-vs-claude-code-vs-windsurf-vs-copilot-6

4. LangGraph + MCP + A2A: The Production Multi-Agent Stack, Now with Standards

【Technical Core】
A new freeCodeCamp long-form guide (April 2026) codifies the emerging production stack: LangGraph for stateful agent orchestration (SQLite checkpointing, deterministic control flow), MCP (Model Context Protocol, now Linux Foundation-governed) for standardized tool access, and A2A protocol (Google’s agent-to-agent standard, 150+ organizations) for cross-framework agent coordination. The reference implementation — a “Learning Accelerator” with 4 specialized agents (Planner, Explainer, Quiz Generator, Progress Coach) — demonstrates tool-calling loops, dual-temperature LLM usage, and human-in-the-loop interrupt() patterns.

【Why It Matters】
The agent framework wars (LangChain vs. CrewAI vs. AutoGen) are giving way to protocol-level standardization. MCP for tools, A2A for agent communication, LangGraph for orchestration — this is shaping up to be the TCP/IP of the agent era. If you’re building multi-agent systems in 2026, this is the reference architecture to benchmark against.

🔗 https://www.freecodecamp.org/news/how-to-build-a-multi-agent-ai-system-with-langgraph-mcp-and-a2a-full-book

5. Gemini 3.5 Flash Drops: 4x Faster Than Frontier, New Default for Google Search AI Mode

【Technical Core】
Google I/O 2026 (May 19) launched Gemini 3.5 Flash, now the default model for the Gemini app and Google Search’s AI Mode. Key specs: ~4× faster output generation than other frontier models, outperforms Gemini 3.1 Pro on key benchmarks, and introduces Gemini Omni — a multimodal world-model family targeting AGI, with video I/O support live and image/text generation coming. Also shipped: Gemini Spark (24/7 cloud-resident personal agent with 30+ MCP tool integrations for Google AI Ultra subscribers) and GPT-Realtime-2 (128K context real-time audio agent, parallel tool calls with audio feedback).

【Why It Matters】
Speed is a capability. A 4× generation speed advantage at frontier quality unlocks interactive agent use cases (voice-driven coding, real-time agent chains) that were previously bottlenecked by latency. Meanwhile, Gemini Spark’s always-on architecture signals Google’s answer to the “persistent agent” race kicked off by Devin and Windsurf 2.0.

🔗 https://github.com/Zijian-Ni/awesome-ai-agents-2026

6. Embodied AI Goes Industrial: SAE World Congress 2026 White Paper + ROS-LLM Framework

【Technical Core】
Two converging signals this month: (1) arXiv:2605.10653 — white paper from the SAE 2026 “Embodied AI in Action” panel (automotive, robotics, AI safety experts) framing embodied AI as a systems-engineering challenge requiring lifecycle governance, not just better models. (2) Nature Machine Intelligence (March 2026) publishes an open-source ROS-LLM framework that bridges LLMs to the Robot Operating System: automatic decomposition of natural language instructions into atomic robot actions, dual execution modes (inline code + behavior trees), imitation-based skill learning, and self-improvement via human/environment feedback. Code: http://github.com/huawei-noah/HEBO/tree/master/ROSLLM

【Why It Matters】
Embodied AI is exiting the “cool demo” phase and entering the “where’s the governance framework” phase. The combination of a formal SAE white paper (industry standards body) and a production-grade open-source ROS-LLM release (Huawei, Nature-published) means 2026 is the year embodied AI starts shipping in real products — not as research prototypes, but as engineered systems with lifecycle safety cases.

🔗 https://arxiv.org/abs/2605.10653
🔗 https://www.nature.com/articles/s42256-026-01186-z

7. The 2026 AI Agent Landscape: 400+ Tools, 30 Commits, 3 Languages — and Counting

【Technical Core】
The awesome-ai-agents-2026 GitHub repository (Zijian-Ni, May 2026 update) now tracks 400+ agent frameworks, models, protocols, and tools across English/Chinese/Japanese. Standouts this month: OpenClaw v2026.5.12 (personal AI agent platform, 8K+ stars, MCP-native), Mastra (TypeScript-first, 21K+ stars), Dify (55K+ stars, drag-and-drop agent builder), and OpenAI Agents SDK (major April 2026 update: native sandbox execution, first-class MCP integration, sub-agent handoff patterns). Microsoft’s merged AutoGen + Semantic Kernel “Microsoft Agent Framework” hits GA in Q1 2026.

【Why It Matters】
If you’re evaluating agent frameworks in mid-2026, the ecosystem has bifurcated into two camps: (1) protocol-native frameworks that treat MCP/A2A as first-class citizens, and (2) legacy frameworks that are retrofitting protocol support. The awesome list is the fastest way to spot which camp a given tool falls into — and that distinction will determine whether your agent stack survives the next 12 months of protocol standardization.

🔗 https://github.com/Zijian-Ni/awesome-ai-agents-2026

How I Secured Internal Microservice Calls Without Passing JWTs

When you build a microservices backend, most security tutorials cover one thing well: securing the public gateway. Validate the JWT, extract the user identity, reject bad requests.

What they don’t cover is what happens after the gateway.

I hit this problem while building a productivity app with four microservices — Gateway, Auth, User, and Core — each with their own database.

The Problem

Core Service needed user data from User Service. Simple enough. But the JWT was already consumed at the gateway. And my scheduled jobs had no HTTP request context at all.

Three options came to mind. All had real problems.

Option 1 — Forward JWTs
Works for simple request chains. Breaks completely for scheduled tasks and async flows. There’s no token to forward when a scheduler fires at 3am.

Option 2 — RequestContextHolder
Thread-local storage. Disappears the moment execution becomes async or event-driven. Unreliable by design.

Option 3 — Plain X-User-Id headers
Looks clean. Is dangerous. Any external caller can send X-User-Id: admin123 and impersonate anyone.

The Solution — Internal Trust Protocol

Instead of propagating JWTs, I designed a lightweight trust model based on network boundaries.

The principle: authenticate once at the edge. Let the private network enforce trust internally.

Caller side:

client.get()
    .header("X-User-Id", userId)
    .header("X-Internal-Call", "true")
    .retrieve()

Receiver side checks both headers. If present, builds SecurityContext directly and skips JWT validation entirely.

Why This Isn’t Spoofable

The gateway strips X-Internal-Call from all external traffic. Internal services are not publicly exposed. External callers can never reach downstream services directly.

The trust model becomes:

External → Gateway (JWT validation) → Internal Services (trust headers)

The network boundary itself is part of the security model.

Results

Scheduled jobs call services safely. Async flows work. Services stay loosely coupled. Authentication complexity lives exactly where it belongs — at the edge.

Full architecture diagram, comparison table, and complete code walkthrough:
👉 https://anupamkushwaha.me/blog/how-i-secured-internal-microservice-calls-without-passing-jwts

Stop Mixing Them Up: SLI vs SLO vs SLA Explained

I used these three terms interchangeably, and many people around me did the same. One day, I decided to sit down and properly understand the difference between them. If you have also thought they were basically the same thing, welcome to my first article.

When building reliable systems, engineers often hear terms like SLI, SLO, and SLA used interchangeably. They are related, but each serves a very different purpose.

A simple way to think about them:

  • SLI = What is happening
  • SLO = What we want
  • SLA = What we promise (and pay for if broken)

Let’s break this down with a real example.

## Tier 1: SLI (Service Level Indicator) : The Metric

An SLI is a quantifiable, real-time measurement of a system’s performance. It answers questions like: “How is the system performing right now?”

In system design, SLIs are usually expressed as a percentage:

SLI = (Good Events / Total Events) × 100

Example: Checkout service latency

  • We define a “good event” as a checkout request completed in under 500ms.
  • SLI formula: (Number of checkout requests completed in under 500ms / Total checkout requests) × 100.
  • Current value: 99.5%.

This tells us the current health of the system, but it does not tell us whether that number is acceptable.

## Tier 2: SLO (Service Level Objective) : The Internal Target

An SLO takes an SLI and adds a target value and a time window. It is an internal goal set by engineering and product teams.
It answers: “What level of performance do we need to maintain to keep users happy?”

Example

  • SLO: “The checkout latency SLI must be >= 99% over a rolling 30-day window.”

The gap between 100% and the SLO, in this case 1%, is called the error budget.

This error budget allows teams to:

  • Release new features.
  • Run experiments.
  • Take controlled risks.

As long as the system stays within the SLO, the team is operating safely. Since our current SLI is 99.5%, we are comfortably meeting the SLO.

## Tier 3: SLA (Service Level Agreement) : The Business Contract

An SLA is a formal agreement between a service provider and a customer. It defines guarantees and penalties.
It answers: “What happens if the system fails, and what will it cost us?”

SLAs are typically less strict than SLOs because violating them has financial or legal consequences.

Example

  • SLA: “We guarantee that the checkout latency SLI will be >= 95% over a monthly billing cycle. If it drops below 95%, we will provide a 15% service credit on the monthly bill.”

In short:

  • SLO protects user experience.
  • SLA protects the business relationship.

Why the distinction matters

Many engineers understand the terms individually, but confusion happens when they are treated as interchangeable.

SLI measures reality, SLO defines the internal target, and SLA defines the external consequence. Understanding that distinction helps teams design systems that are reliable, practical, and aligned with both user expectations and business goals.

Please like and share this article if you found it helpful 🙂

Rebuilding My Engineering Mind

A 5-Year Roadmap Back to Strong Fundamentals

In 2018, I started my bachelor’s degree in Computer Engineering with almost no real passion for it.

At the time, I was just there to get a degree.

I attended classes, did assignments, passed exams, and moved forward because that was the expected path. Programming was simply another subject to survive. But somewhere along the way, something changed. Little by little, I started enjoying the process of building things. Not just writing code, but understanding systems. Understanding why things break. Understanding why some designs scale while others collapse like wet cardboard castles under production traffic.

By 2024, I landed an internship as a junior Angular developer. That experience changed a lot for me. For the first time, programming stopped feeling academic and started feeling alive. Real projects. Real problems. Real consequences. I finally felt connected to the craft.

Then came the era of LLMs.

Like many people, I started using them to speed things up. At first, it was harmless. Small questions. Boilerplate. Debugging assistance. Then slowly, without realizing it, I became what the internet calls a “vibe coder.”

I stopped solving problems properly.

Instead of thinking through implementation details, I started outsourcing the thinking itself. Homework became prompts. Bugs became prompts. Entire features became prompts. And because the results worked most of the time, I convinced myself I was still learning.

But I wasn’t.

After finishing my bachelor’s degree, I got accepted into a Cybersecurity master’s program. That experience gave me a completely different perspective on software engineering. I started seeing systems from the security side: trust boundaries, attack surfaces, infrastructure, protocols, reliability, operational risk. Software stopped being “just code.” It became architecture.

The program was difficult enough that I had to leave my internship and fully focus on studying.

And somewhere during all of this, I lost something important.

One day, I decided to write a simple C++ program I had written before. Nothing complicated. Just basic logic.

My mind went blank.

Not “I forgot a syntax detail” blank.

I mean genuinely blank.

I realized I had become dependent on AI to the point where I had weakened my own ability to think through code independently. That moment hit me harder than any failed exam ever could. It felt like discovering that the engine inside the machine had quietly rusted while the dashboard lights still looked fine.

That was the wake-up call.

So I made a decision:

I’m going back to fundamentals. Properly this time.

Not for grades.
Not for tutorials.
Not for productivity dopamine.
For mastery.

I built a long-term roadmap for myself. A 4 to 5 year journey focused on rebuilding my foundations from the ground up and growing into the kind of engineer I actually want to become.

The goal is not speed.

The goal is depth.

The Roadmap

Phase 1: C and Linux Fundamentals

This is where I start rebuilding my low-level understanding of systems. Memory, processes, pointers, filesystems, shells, tooling. No abstractions to hide behind.

Phase 2: Python, Bash, and Operating Systems

Automation, scripting, internals, process management, scheduling, concurrency, permissions. Learning how machines actually behave beneath the UI layer.

Phase 3: Networks and the Internet

Protocols, routing, DNS, TCP/IP, HTTP, sockets, infrastructure communication. The invisible highways everything depends on.

Phase 4: Docker and Kubernetes

Containers, orchestration, deployment models, scalability, infrastructure management. Modern software engineering’s giant mechanical spiderweb.

Phase 5: CI/CD and Security

Pipelines, DevSecOps practices, hardening, monitoring, secrets management, secure deployment workflows.

Phase 6: Rust

Because I want to learn systems programming with modern safety guarantees while still staying close to the metal.

Phase 7: Databases and Distributed Systems

Replication, consistency, CAP theorem, transactions, fault tolerance, system design. This is where software starts becoming architecture.

Phase 8: C++

Returning to the language that exposed my weaknesses in the first place. This time with real understanding behind it.

Phase 9: Elixir and Haskell

Not because they are trendy, but because they force different ways of thinking. Concurrency models. Functional paradigms. Strong abstractions. Mental expansion through controlled suffering.

Alongside all of this, I want to keep following the thing that made me love engineering in the first place:

Game development.

At least two days every week will be reserved for building games, experimenting, and creating weird little worlds that may never ship but will keep the spark alive. I do not want this journey to become pure optimization and infrastructure grayness. A machine without creativity eventually becomes a factory.

This entire roadmap is going to take years.

And honestly, that’s fine.

For the first time in a long while, I’m not chasing shortcuts anymore.

I’m chasing competence.

My short-term goal is to land a DevOps role. My long-term goal is to become a system architect capable of designing reliable, scalable, and secure systems from the ground up.

And to keep myself accountable, I’m going to document the journey publicly through weekly posts. Progress, failures, discoveries, frustrations, all of it.

Maybe this series will help someone else who feels stuck in the same cycle.

Maybe it will simply become a record of one engineer rebuilding his foundations one layer at a time.

Either way, this is the start of something new.

The terminal is open.
The cursor is blinking.
And this time, I actually want to understand what I’m typing