I Migrated Our API From REST to GraphQL. Here’s What Nobody Warned Me About.

I Migrated Our API From REST to GraphQL. Here’s What Nobody Warned Me About.

We spent 3 months migrating our main API. The benefits are real. So are the headaches nobody talks about.

Why We Switched

REST endpoints were multiplying like rabbits. Mobile needed different shapes than web. The versioning nightmare was real.

What Actually Happened

Week 1-4: Schema design. This is where most teams underestimate. A bad schema haunts you forever.

Week 5-8: Resolver implementation. N+1 query problems hit hard. We had to implement DataLoader everywhere.

Week 9-12: Client migration. Web first, then mobile. Both had different caching strategies.

The Real Benefits

Query-level permissions. No more endpoint explosion. Type safety across the stack. The mobile team stopped asking us for new endpoints.

What Nobody Warns You About

Error handling is brutal. A partial success in GraphQL means you’re sending 200 with partial errors in the response body. Clients don’t expect that.

Caching is harder. HTTP caching doesn’t apply. You need application-level caching.

Monitoring is different. Each query is unique. Traditional request metrics don’t tell you much.

Would I Do It Again?

For our use case, yes. For a simple CRUD API? No.

What’s your API architecture?

Fixing AADSTS700082: refresh tokens expiring after 12 hours in Azure Entra External ID

Conditional Access Policies

Fixing AADSTS700082: refresh tokens expiring after 12 hours in Azure Entra External ID

At libelo we use Azure Entra External ID (CIAM) (Customer Identity and Access Management) as the identity provider for our React Native mobile app. We use the Native Authentication API for email + password and the browser-delegated flow for Google and Apple federation. All three flows request offline_access to get a refresh token, exactly as the documentation prescribes for a native/public client.

After our first ‘friends & family’ release, we started seeing users being logged out of the app overnight. Open the app the next morning, get prompted to sign in again, close the app. Not great for the experience.

The cause was a refresh token lifetime that did not match what the docs describe. This post explains the symptom, what we ruled out, and the actual fix – which turns out to be a configuration setting in Conditional Access, not a bug (anymore) in External ID.

If you arrived here because you are seeing AADSTS700082 with “The refresh token has expired due to inactivity” and exactly 12:00:00 of inactivity reported in the error message, you are in the right place.

The symptom

The user signs in, the app receives an access_token and a refresh_token, everything works. The next time the app comes to foreground – typically the next morning – the silent token refresh call fails with this response:

POST https://yourtenant.ciamlogin.com/yourtenant.onmicrosoft.com/oauth2/v2.0/token
→ 400

{
  "error": "invalid_grant",
  "error_codes": [700082],
  "error_description": "AADSTS700082: The refresh token has expired due to inactivity.
    The token was issued on 2026-05-03T12:11:26.5799126Z and was inactive for 12:00:00.
    Trace ID: ...
    Correlation ID: ...
    Timestamp: 2026-05-04 05:33:58Z",
  "error_uri": "https://yourtenant.ciamlogin.com/error?code=700082"
}

The interesting part is the constant 12:00:00. Every developer hitting this issue reports the same value. The token is killed exactly twelve hours after issue if the app has not refreshed it in the meantime. For a mobile app that the user opens once a day, that means re-authentication every single morning.

This is the same behaviour discussed in the Microsoft Q&A thread that has been open since 2024 and in this StackOverflow question. Where these threads mostly talk about OTP (One Time Password), we reproduced the behaviour across every identity provider configured in our user flow.

What we ruled out first

The refresh token lifetime documentation lists exactly two scenarios where a refresh token is short-lived rather than getting the 90-day sliding window a native public client should get:

  • SPA platform redirect URIs. When the redirect URI is registered under spa.redirectUris, the token endpoint enforces Single-Page Application rules and returns a 24-hour refresh token with no inactivity reset.
  • Email OTP sign-up flow. A specific exception that historically produced short-lived tokens.

Our app registration uses a custom URI scheme (libelo://auth/callback) registered under publicClient.redirectUris, not under spa.redirectUris. And the same 12:00:00 cap reproduces on email + password, Google federation, and Apple federation – so this is not the email OTP edge case either.

In other words, by the documentation we should be getting a 90-day refresh token with a sliding inactivity window. We are getting 12 hours hard-capped. Something else was at play.

The actual cause: Sign-in Frequency

After raising this internally with the Microsoft product group, the answer came back: ‘This is not a refresh-token bug. It is the default behaviour of the Sign-in Frequency (SIF) capability in External ID.’

Sign-in Frequency controls how often a user is required to reauthenticate, independent of whether the refresh token itself has technically expired. The session lifetime policy can – and by default in External ID does – cut the refresh token’s effective inactivity window down to a value much shorter than the 90 days the underlying token would otherwise support.

That is why the error always says exactly 12:00:00: it is not the refresh token’s own lifetime, it is the session lifetime policy invalidating it.

The fix is to configure a Conditional Access adaptive session lifetime policy that matches the experience you want on mobile.

The relevant documentation is here:

The fix: configuring Sign-in Frequency

In the Entra admin center of your specific directory, open Security → Protection → Conditional Access → Policies in the External ID tenant.

This opens a screen like this:

Conditional Access Policies

Create a new policy with the following:

Users. Assign to the user population this should apply to. For a customer-facing app this is typically All users (or a specific group if you are rolling this out gradually).

Target resources. Select your application registration – the same client ID the mobile app uses to authenticate. Do not apply this to All cloud apps in a CIAM tenant; you want the policy scoped to the customer-facing app, potentially you want other policies for other platform applications.

Target resources

Conditions. Optionally restrict to Mobile apps and desktop clients if you want to leave web sessions on a different cadence.

Session controls → Sign-in frequency.

  • Set the mode to Periodic reauthentication.
  • Set the value to something appropriate for your app. We use 90 days to match the underlying refresh token lifetime, which gives users the “always signed in” experience you would expect from any modern mobile app.
  • Enable Persistent browser session as well if you are also covering web flows.

Session controls

Save the policy and give it a few minutes to propagate. From that point on, refresh tokens issued under that policy will respect the new sign-in frequency value rather than the default 12 hours. (so yes, your users will have to do the authentication round trip again)

Validating it works

The access token , nor the refresh token, specify the expiration time of the refresh token. So, to validate, sign in, leave the app closed overnight, open it the next morning, and check that the silent refresh succeeds without prompting. (I tested this with 3 ios simulators, one for a gmail user, one for apple and one for username/password).

If you want a faster feedback loop, you can also temporarily set the SIF value to one hour, sign in, wait an hour, and confirm the refresh now fails at one hour instead of twelve. That proves the policy is being evaluated against your app registration. Then put it back to 90 days (or whatever value you actually want).

You can also tail the Sign-in logs in the Entra admin centre and look at the Conditional Access tab on a sign-in event. The policy you just created should show up as Applied (or Not applied with a reason, which is what you want to know if it is not working yet).

Sign in logs

A few things worth knowing

The 12-hour default is not documented anywhere obvious. This is the bit that cost everyone the most time. The refresh token documentation describes the 90-day sliding window and the SPA / OTP exceptions, and stops there. The fact that the default External ID session lifetime caps refresh at 12 hours regardless of what the underlying token allows is buried in the Conditional Access docs, not in the token docs. So if you went looking in the obvious place, you may not have found it.

The same applies to the workforce tenant flavour of Entra, but the defaults there are different and the typical workforce scenario – a desktop with a Windows session keeping the device active – masks the symptom. On mobile, where the app is genuinely idle for hours between sessions, the cap becomes visible immediately.

This is not specific to the Native Authentication API. We reproduced the same behaviour on the standard browser-delegated authorization_code + PKCE flow. The fix is the same in both cases because the policy is evaluated at the token endpoint, not at the authentication endpoint.

Summary

If you are building a mobile app on Azure Entra External ID and your users are being kicked back to the sign-in screen every twelve hours, the solution was not lying in waiting for a fix for the refresh token duration. The refresh token is behaving as the policy says it should. The behaviour you want is one Conditional Access policy away.

I do think the default value here is the wrong one for a CIAM scenario – a customer mobile app is by definition idle for long stretches, and twelve hours is short enough that nearly every user will notice it – but at least there is a configuration path that solves it cleanly without having to move off the platform.

Hope this saves someone frustrations and time loss.

Grafana’s GitHub Token Incident: 5 Steps DevOps Teams Can Take to Recover Faster

If the recent Grafana Labs GitHub token incident caught your attention, it should.

A compromised GitHub token is not just a source code problem. For many DevOps and platform teams, GitHub is where infrastructure is defined, workflows are triggered, deployments are approved, and cloud changes are controlled.

Terraform files. GitHub Actions workflows. Branch protection rules. Repository permissions. Deployment environments. Webhooks. GitHub App integrations.

They all sit inside or around GitHub.

So when a GitHub environment is compromised, deleted, misconfigured, or held hostage, restoring the repository is only step one.

You also need to restore the configuration that makes the repository usable, secure, and ready for deployment.

That is exactly why GitHub configuration disaster recovery is becoming part of the modern cloud resilience conversation.

Here are five practical steps DevOps teams can take to protect GitHub configuration and recover faster from ransomware-style incidents.

TL;DR: 5 GitHub DR Steps to Take Tomorrow 🚀

  1. Audit what GitHub really controls
  2. Back up repositories beyond a simple clone
  3. Capture the configuration around the repo
  4. Build a separate recovery path for secrets and tokens
  5. Run a mini GitHub recovery drill

1. Audit What GitHub Really Controls 🔍

Start with visibility.

Most organizations know which repositories hold application code. Fewer teams know which repositories control production infrastructure, CI/CD workflows, deployment approvals, cloud permissions, and security policies.

That gap matters during recovery.

If a GitHub token is compromised, your team needs to know which repositories are business-critical and which systems depend on them.

A small internal tool repo may not need the same recovery priority as the repository that controls Terraform modules, production workflows, or deployment pipelines.

Tomorrow, map your GitHub environment by recovery priority.

Identify the repositories that control:

  • Production Infrastructure as Code
  • CI/CD workflows
  • Deployment scripts
  • Security policies
  • Cloud account access
  • GitHub Actions workflows
  • Environment approvals
  • Shared Terraform modules
  • Operational runbooks

This gives your team a clear recovery order.

Without this inventory, recovery becomes guesswork. Engineers waste time deciding what matters most while the incident is already happening.

In ransomware-style incidents, that delay can increase downtime, slow containment, and put unnecessary pressure on DevOps and security teams.

A GitHub disaster recovery plan starts with knowing what GitHub actually runs.

2. Back Up Repositories Beyond a Simple Clone 🧱

Once you know which repositories matter, back them up properly.

A regular clone may help an engineer keep working locally, but it is not enough for a complete recovery plan.

Critical repositories should be backed up with full mirror copies that preserve:

  • Branches
  • Tags
  • Refs
  • Repository history

For example:

git clone --mirror [email protected]:org/critical-repo.git

If your team uses Git LFS, those objects must be included too.

Otherwise, you may restore a repository that looks complete but is missing large files, binaries, or assets used by pipelines.

Tomorrow, create external mirror backups for your highest-priority repositories.

Store them outside the same GitHub organization and identity boundary.

If the same compromised token, user, or GitHub organization can reach both your production repository and your backup, the backup is not isolated enough.

This is the same principle used in cloud disaster recovery:

Backups must be separate, restorable, and tested.

GitHub repository backup checklist

A repository backup should prove that teams can restore full history, branches, tags, and recover into a clean environment.

3. Capture the Configuration Around the Repo ⚙️

Repository backup protects your code.

But it does not automatically protect the GitHub settings that make the repo usable.

Important configuration often lives around the repository, including:

  • Branch protections
  • Rulesets
  • Deployment environments
  • GitHub Actions permissions
  • Repository variables
  • Webhooks
  • Team access
  • GitHub Apps
  • Required reviewers
  • Environment approvals

If these settings are changed or missing during recovery, your code may be restored — but deployments, reviews, and permissions can still break.

Tomorrow, pick your most critical production repositories and export the GitHub settings around them.

Store those exports as versioned snapshots outside GitHub, so your team has a known-good reference if repo configuration is changed, deleted, or compromised.

During an incident, engineers should not have to rebuild branch protections, permissions, and webhooks from memory.

Manual reconstruction is slow, risky, and easy to get wrong under pressure.

Restoring code is not the same as restoring operations.

For teams that rely on GitHub as part of their infrastructure delivery process, configuration disaster recovery for GitHub helps close the gap between restoring code and restoring the operational controls around that code.

4. Build a Separate Recovery Path for Secrets and Tokens 🔐

Secrets need a different recovery plan.

GitHub secrets are critical for CI/CD and deployment workflows, but GitHub is not a complete backup source for them.

Teams may be able to see secret names or metadata, but they cannot simply export secret values back out of GitHub.

That means GitHub should not be the only place that knows the credentials required to rebuild your workflows.

Tomorrow, review the secrets and tokens used across your GitHub environment, especially the ones connected to production systems:

  • Cloud provider credentials
  • Deployment keys
  • Container registry credentials
  • Webhook secrets
  • GitHub App private keys
  • CI/CD service tokens
  • Security scanner tokens
  • SaaS integration credentials

Each one should have an external source of truth, such as a secrets manager, vault, or controlled recovery process.

This is also the time to reduce token risk.

The Grafana incident is a reminder that one compromised token can create a serious blast radius.

So ask:

  • Who owns each token?
  • Does it still need access?
  • Is the scope too broad?
  • Can its lifetime be reduced?
  • Can stale access be removed?
  • Is there a recovery process if it is rotated or revoked?

Never let one token become the single point of failure for your GitHub environment.

5. Run a Mini GitHub Recovery Drill 🧪

Do not wait for an incident to test your GitHub recovery plan.

Pick one critical repository tomorrow and run a small recovery drill.

The goal is not to simulate a full company-wide breach. The goal is to prove that one important repository can be restored into a clean, trusted state.

Your drill should test whether your team can:

  • Restore the repository from backup
  • Reapply branch protections and rulesets
  • Recreate deployment environments
  • Reconnect webhooks
  • Re-seed secrets from the external source of truth
  • Run a GitHub Actions workflow
  • Confirm the right teams have access
  • Confirm the right approvals are enforced

This drill will expose the real gaps.

Maybe the repository restores, but the workflow fails.

Maybe a webhook secret is missing.

Maybe the branch protection rules were never backed up.

Maybe a GitHub App was installed years ago and no one knows who owns it.

Maybe only one engineer knows how to reconnect the deployment pipeline.

That is exactly why the drill matters.

A backup is only useful if the restore works.

For DevOps teams, recovery should not depend on memory, screenshots, or one engineer who understands the setup.

It should be documented, versioned, and repeatable.

Why GitHub Recovery Is Now Part of Cloud Disaster Recovery ☁️

Traditional backup strategies often stop at the repository.

But in a real incident, missing GitHub configuration can delay recovery, break deployments, and create compliance risk.

The Grafana incident is a reminder that modern disaster recovery needs to protect both the code and the configuration around it:

  • Workflows
  • Approvals
  • Permissions
  • Webhooks
  • Deployment controls
  • Infrastructure definitions
  • Cloud access paths

GitHub is no longer just where code lives.

For many teams, it is part of the production control plane.

That means GitHub recovery should be part of your broader cloud disaster recovery strategy.

Recovering Code Is Not Enough

If your GitHub environment is compromised, your team needs more than a repo backup.

You need to know:

  • Which repositories matter most
  • Which systems depend on them
  • Which configurations are required to operate safely
  • Which secrets must be restored from outside GitHub
  • Whether your recovery process actually works

ControlMonkey helps DevOps teams strengthen Cloud Configuration Disaster Recovery by continuously capturing infrastructure configuration, detecting drift, and enabling fast recovery from known-good states.

That includes extending recovery beyond cloud resources into the systems that control infrastructure delivery, including GitHub. You can read more about ControlMonkey’s GitHub Configuration Disaster Recovery announcement.

Because modern recovery is not just about restoring files.

It is about restoring control.

👉 Learn more about ControlMonkey’s Cloud DR products and Cyber resilience platform.

Discussion 💬

How does your team handle GitHub recovery today?

Do you back up only repositories, or also the configuration around them — branch protections, webhooks, environments, rulesets, and deployment controls?

Would love to hear how other DevOps and platform teams are approaching this.

Samsung, Google, Gentle Monster ve Warby Parker iş birliğiyle hazırlanan ilk akıllı gözlüklerini görücüye çıkardı

Samsung, Google, Gentle Monster ve Warby Parker iş birliğiyle hazırlanan ilk akıllı gözlüklerini görücüye çıkardı

Samsung, Google, Gentle Monster ve Warby Parker iş birliğiyle hazırlanan ilk akıllı gözlüklerini görücüye çıkardıSamsung, “Google, Gentle Monster ve Warby Parker” iş birliğiyle hazırlanan ilk akıllı gözlüklerini görücüye çıkardı. Android XR işletim sisteminin ve sesli iletişim özelinde Gemini’ın temel alındığı gözlükler, resmi açıklamaya göre sonbahar döneminde satışa sunulacak. Kamera, mikrofon ve hoparlör ile donatılan modeller, kişilerin Gemini ile sohbet etmesine ve gördükleri hakkında yapay zekadan gerçek zamanlı olarak bilgi almasına imkan tanıyacak. Akıllı telefonlarla kablosuz olarak bağlantı kuracak (iOS ve Android desteği) ve cihazlara gelen bildirimleri kullanıcılara özet halinde aktarabilecek gözlükler, bünyelerinde herhangi bir ekran barındırmıyor. Son bilgilere göre Samsung bünyesinde ekran olan akıllı gözlükler üzerinde de çalışmalar gerçekleştiriyor ve bu gözlükleri 2027’de satışa sunmayı hedefliyor. Bu arada Android XR ekosistemine başka firmalar da destek veriyor. Bunlardan biri XREAL ve şirket Android telefonlara “kablo” ile bağlanan ekranlı bir gözlük modeli geliştiriyor. İlk gösterimi 8 Aralık 2025’te yapılan “Project Aura” kod adlı ürünün fiyatı henüz belli değil ancak yıl bitmeden önce satışa sunulmuş olacak. Get […]

Samsung, Google, Gentle Monster ve Warby Parker iş birliğiyle hazırlanan ilk akıllı gözlüklerini görücüye çıkardı yazısı ilk olarak Teknoloji haberleri – LOG üzerinde yayınlandı.

Diffie-Hellman Key Exchange Explained: Sharing a Secret in Public

Two people who have never met, talking over a line that everyone can hear, can still agree on a secret number that no eavesdropper can recover. When Whitfield Diffie and Martin Hellman published this idea in 1976, it sounded like a contradiction. It is now the quiet foundation of nearly every secure connection you make.

Before 1976, encryption had a chicken-and-egg problem. To send an encrypted message you needed a shared key, but to share a key securely you needed an already-secure channel — which is the very thing you were trying to build. Spies exchanged codebooks in person; banks couriered keys in locked briefcases. The internet had no such luxury.

Diffie-Hellman key exchange broke the loop. It lets two parties derive a shared secret over a completely public channel, such that an attacker recording every byte of the conversation still cannot compute the secret. It does not encrypt anything by itself — it produces the key that something else will use to encrypt.

The Paint-Mixing Intuition

The classic analogy is mixing paint. Suppose Alice and Bob want a shared secret color, and everyone can watch the cans being passed around.

  • They publicly agree on a common starting color — say, yellow. Everyone knows it.
  • Alice privately picks a secret color and mixes it into the yellow. Bob does the same with his own secret. Each sends the resulting mixture to the other.
  • Alice takes Bob’s mixture and adds her secret color again. Bob takes Alice’s mixture and adds his. Both arrive at the same final color — yellow plus Alice’s secret plus Bob’s secret.

An eavesdropper saw the yellow base and both mixtures fly by, but separating a mixed paint back into its components is hard. They cannot reconstruct the final blend without one of the private colors. That irreversibility — easy to mix, hard to un-mix — is the whole trick.

The Real Mathematics

The cryptographic version replaces paint with modular exponentiation. Both parties agree publicly on a large prime p and a base g. Then:

  1. Alice picks a secret integer a and sends A = g^a mod p.
  2. Bob picks a secret integer b and sends B = g^b mod p.
  3. Alice computes B^a mod p. Bob computes A^b mod p.

Both results equal g^(ab) mod p — the shared secret. The eavesdropper knows p, g, A, and B, but to find g^(ab) they would need to recover a or b from A or B. That is the discrete logarithm problem: trivial to compute g^a, infeasible to reverse for large primes.

The asymmetry that protects you: Modular exponentiation is a one-way street. With a 3072-bit prime, computing g^a takes milliseconds, but the fastest known algorithm to invert it would take longer than the age of the universe on classical hardware. The security rests entirely on that gap.

The Achilles’ Heel: Authentication

Plain Diffie-Hellman has a fatal weakness if used alone: it provides no authentication. Because the parties never verify each other’s identity, an active attacker sitting in the middle can run two separate exchanges — one with Alice pretending to be Bob, one with Bob pretending to be Alice — and relay messages between them. This is the man-in-the-middle attack, and it defeats the secrecy entirely.

Diffie-Hellman guarantees that you share a secret with someone. It says nothing about who. Establishing who requires a separate authentication layer — certificates, signatures, or a verified fingerprint.

In practice this is solved by signing the exchanged values with a previously trusted key. In TLS, the server signs its Diffie-Hellman parameters with the private key tied to its certificate, which your browser validates against a chain of certificate authorities. In messaging apps, identity keys and out-of-band fingerprint verification serve the same role.

Ephemeral Diffie-Hellman and Forward Secrecy

The most important modern refinement is ephemeral Diffie-Hellman (DHE, or ECDHE in its elliptic-curve form). Instead of reusing a long-lived secret, the parties generate fresh, random a and b values for every session and discard them afterward.

This delivers forward secrecy: even if an attacker later steals a server’s long-term private key, they cannot decrypt past recorded sessions, because the per-session secrets no longer exist anywhere. Each conversation is sealed in its own moment. This single property is why TLS 1.3 mandates ephemeral key exchange and removed the older static-RSA handshake entirely.

Finite Fields vs. Elliptic Curves

The original Diffie-Hellman works in a finite field of integers modulo a prime. The modern variant performs the same dance using the point arithmetic of elliptic curves — ECDH — which achieves equivalent security with far smaller keys.

Variant Operates over Typical key size
Finite-field DH Integers mod a large prime 2048–3072 bits
Elliptic-curve DH (ECDH) Points on a curve 256 bits (e.g. X25519)

X25519 — ECDH over Curve25519 — is now the workhorse of secure connections, used in TLS 1.3, SSH, WireGuard, and the handshake of modern messengers. When you read that an app uses “X25519 for key agreement,” that is ephemeral elliptic-curve Diffie-Hellman.

The Quantum Horizon

Like all discrete-logarithm and factoring schemes, Diffie-Hellman is vulnerable to a sufficiently large quantum computer running Shor’s algorithm. None exists yet, but the “harvest now, decrypt later” concern is real: traffic recorded today could be broken in the future. The response is hybrid key exchange — running a classical X25519 exchange alongside a post-quantum algorithm and combining both outputs, so an attacker must break both to recover the key.

Why It Still Matters

Diffie-Hellman is half a century old and more relevant than ever. Every HTTPS page load, every SSH session, every encrypted message handshake begins with some descendant of that 1976 idea: two parties building a shared secret out in the open, protected only by a math problem that is easy to walk forward and brutally hard to walk back.

At Haven, ephemeral key agreement underpins the encrypted transport beneath our app, and the same family of primitives feeds the group key management in the MLS protocol. Understanding the handshake is the clearest way to see what your encryption actually protects — and what it leaves to the authentication layer above it.

Originally published at havenmessenger.com

Todo en C es comportamiento indefinido: 30 años de bugs por diseño

Thomas Habets lleva treinta años escribiendo C y C++ casi a diario. Trabaja en Google, lee propuestas del comité ISO, escucha podcasts del lenguaje y disfruta del oficio. Su conclusión publicada esta semana en su blog personal es contundente: nadie puede escribir código C correcto. Y no lo dice contra los principiantes — lo dice contra sí mismo.

El ensayo recorre los rincones del comportamiento indefinido que la mayoría de desarrolladores ignora y lo conecta con la migración masiva de infraestructura crítica hacia Rust que vimos durante 2026. La tesis es directa: el entorno de 1972 ya no es el entorno de hoy.

TL;DR

  • Thomas Habets, ingeniero con 30 años en C/C++, sostiene que nadie escribe código no trivial libre de comportamiento indefinido.- El UB no aparece solo con optimizaciones agresivas: significa que el compilador asume que tu código nunca lo invoca, en ningún backend.- Crear un puntero mal alineado ya es UB en C23 §6.3.2.3 — no hace falta dereferenciarlo, el cast solo basta.- isxdigit() recibe un int; pasarle un char con signo negativo es UB. La mayoría del código C en producción no castea a unsigned char.- El desbordamiento de enteros con signo sigue siendo UB en 2026, aunque todos los CPU reales usan complemento a dos.- Microsoft reportó que el 70% de los CVE críticos en Windows entre 2006 y 2018 fueron bugs de memoria prevenibles en lenguajes safe.- Bun, Astral, Cloudflare y módulos del kernel de Linux migraron infraestructura crítica a Rust en los últimos 12 meses.

Qué pasó

Habets publicó en blog.habets.se un ensayo titulado Everything in C is undefined behavior. El texto sostiene que todo programa no trivial escrito en C o C++ contiene comportamiento indefinido (UB, por undefined behavior), y que la industria sigue tropezando con los mismos errores década tras década.

El argumento en sí mismo no es nuevo. Lo distinto es de quién viene y cuándo llega. Habets no es un converso de Rust evangelizando contra el lenguaje viejo — es un practicante veterano de C++ que admite que no puede dominarlo y que argumenta, con casos concretos, que nadie puede. La pieza aterriza además en un momento particular: en los últimos doce meses, la infraestructura más cargada del internet hispano y global — el runtime de Bun, las herramientas Python de Astral, el edge de Cloudflare, módulos críticos del kernel de Linux — migró parcial o totalmente a Rust. Habets conecta esta migración con la imposibilidad práctica de escribir C seguro a escala.
El compilador asume que tu código nunca invoca UB, en cualquier backend.

Contexto e historia

El comportamiento indefinido es una categoría del estándar C que cubre situaciones donde el lenguaje no especifica qué debe ocurrir. La especificación C23 lista decenas de casos: doble free, uso después de liberación, acceso fuera de los límites de un objeto, lectura de memoria sin inicializar, división por cero con enteros, desbordamiento de enteros con signo, modificación de cadenas literales, y un largo etcétera.

La intuición común entre programadores intermedios es que el UB solo aparece cuando se activa la optimización del compilador. Como si el compilador, al detectar UB, dijera deliberadamente AHA, puedo hacer lo que quiera aquí — y al desactivar las optimizaciones, ese impulso desapareciera. Habets enfatiza que esta intuición es incorrecta.

UB no significa que el compilador aproveche tu descuido. UB significa que el compilador puede asumir que tu código nunca invoca UB. La intención que es obvia para un humano leyendo el código no tiene siquiera una manera de ser expresada entre las fases del compilador o entre módulos. El compilador, y el hardware abajo, juegan un teléfono descompuesto con tu intención: puede que termine haciendo lo que querías, pero no hay garantías para hoy ni para versiones futuras.

⚠️ Ojo: el compilador no tiene que implementar casos especiales en su generación de código para situaciones que “no pueden pasar”. Si tu código las dispara, el binario resultante puede tener cualquier comportamiento — incluso uno que pase tests hoy y falle en producción cuando se actualice GCC o Clang.

Casos sutiles que pocos conocen

Habets dedica buena parte del ensayo a casos donde el UB es invisible para un lector experimentado. Tres ejemplos resumen el argumento.

Acceso a un objeto mal alineado

Considerá esta función trivial:

int foo(const int* p) {
  return *p;
}

Si esta función se llama con un puntero no alineado correctamente — probablemente significando una dirección múltiplo de sizeof(int), aunque la definición exacta depende de la implementación — es UB según C23 §6.3.2.3.

En Linux Alpha, en algunos casos esto atrapaba al kernel, que emulaba en software lo que querías hacer. En otros casos crasheaba con SIGBUS. En SPARC, SIGBUS directo. En x86 y amd64 es probable que funcione, e incluso podría ser una lectura atómica — x86 es famosamente permisivo con la coherencia de caché. ¿Qué pasa en ARM, RISC-V o futuras arquitecturas? Habets imagina un futuro CPU con registros especiales para punteros enteros que no use los bits bajos, porque tales punteros no pueden existir desde la perspectiva del lenguaje.

Y acá viene el giro: el problema no está en dereferenciar el puntero. Está en crearlo. Este código ya es UB antes incluso de pasar por foo():

bool parse_packet(const uint8_t* bytes) {
  const int* magic_intp = (const int*)bytes; // UB aquí
  int magic_raw = foo(magic_intp);
  int magic = ntohl(magic_raw);
  // ...
}

El cast es el problema, no la función foo(). Es perfectamente válido para el compilador asignar significado específico — tags de garbage collection, bits de seguridad, identificadores de cache line — a los bits bajos de un int*. Si lo asume y vos pasaste bits arbitrarios, tu programa entró en territorio indefinido sin que ninguna línea de código se vea sospechosa al ojo humano.

isxdigit() con char con signo

La función estándar isxdigit() recibe un int, no un char. Acepta valores de unsigned char y el valor especial EOF. Si pasás un char con signo cuyo valor sea negativo — algo perfectamente posible cuando leés bytes de un archivo UTF-8, de la red o de cualquier fuente binaria — el resultado es UB.

bool bar(char ch) {
  return isxdigit(ch); // UB si ch es negativo
}

La forma correcta requiere casteo explícito a unsigned char antes de pasar el valor:

bool bar(char ch) {
  return isxdigit((unsigned char)ch);
}

¿Cuántas líneas de código C activo en producción en LATAM cumplen esta regla? Probablemente menos del 5%. Y este es uno de los UB más documentados del lenguaje: aparece en la página de cppreference desde hace dos décadas. Si los profesionales que escriben C cada día no lo recuerdan, ¿qué esperanza tienen los demás?

Desbordamiento de enteros con signo

En 2026, todos los procesadores que importan usan representación de complemento a dos. Sumar INT_MAX + 1 en una arquitectura real produce INT_MIN. Pero el estándar de C sigue declarando este caso como UB. El compilador puede asumir que un programador nunca dispara desbordamiento con signo, y optimizar bucles bajo esa premisa.

El ejemplo clásico es un loop con condición i <= INT_MAX: si la variable i es int, el compilador puede asumir que la condición eventualmente se vuelve falsa, porque desbordar es UB y “no puede pasar”. Si vos escribiste el código contando con el wraparound para salir del loop, podés terminar con un loop infinito en release y un loop finito en debug. Y nada en el código fuente revela la diferencia.
El compilador asume que tu código nunca invoca UB y optimiza bajo esa premisa.

El flujo del compilador con UB

Para visualizar por qué desactivar optimizaciones no salva del problema, sirve mirar el flujo conceptual:

graph LR
  A["Codigo C con UB latente"] --> B["Frontend del compilador"]
  B --> C["IR asume codigo valido"]
  C --> D["Optimizador elimina ramas imposibles"]
  C --> E["Backend genera asm"]
  D --> F["Binario impredecible"]
  E --> F

El IR (representación intermedia) carga la asunción de que el código no invoca UB desde la primera traducción. No es algo que se active con -O2. Es algo que está horneado en la semántica del lenguaje desde C89.

Datos y cifras del problema

El ensayo de Habets no sale del vacío. Aterriza en una conversación más amplia con datos concretos:

  • Microsoft reportó en 2019 que el 70% de los CVE críticos parcheados en sus productos entre 2006 y 2018 fueron bugs de memoria — todos prevenibles en un lenguaje memory-safe.- Google publicó cifras similares para Chromium: aproximadamente el 70% de las vulnerabilidades de severidad alta eran bugs de memoria nativa.- Android reportó que las introducciones nuevas de Rust en su base de código tuvieron cero vulnerabilidades de memoria en 2024, mientras que el código C/C++ equivalente habría producido decenas según las estadísticas históricas.- Bun exploró un puerto de Zig a Rust usando agentes de Claude Code en 2026.- Astral, la empresa detrás de uv y ruff, escribió toda su pila de herramientas Python en Rust.- Cloudflare reescribió componentes críticos de su edge en Rust.- El kernel de Linux aceptó Rust como segundo lenguaje oficial en 2024 y desde entonces aceptó drivers y módulos críticos.

Estas decisiones no son moda. Son una respuesta racional al argumento de Habets: si nadie puede escribir C libre de UB, y los compiladores cada año son más agresivos asumiendo que el código no invoca UB, entonces la única forma de tener garantías reales es cambiar de herramienta.

Impacto y análisis

La pieza ya generó respuestas en Hacker News, Lobsters y en el subreddit r/cpp. Los críticos hacen tres puntos válidos.

Primero, existen herramientas de análisis estático y dinámico. UBSan (UndefinedBehaviorSanitizer), AddressSanitizer, ThreadSanitizer y herramientas comerciales como Coverity o PVS-Studio reducen el costo de mantener C seguro. Habets responde que estas herramientas son cinta adhesiva sobre un problema de diseño: detectan UB cuando se ejecuta, pero no garantizan su ausencia. Ningún proyecto C grande logra zero-UB verificado en producción.

Segundo, C tiene un ecosistema y un perfil de rendimiento difícil de replicar. Reescribir todo en Rust es caro y arriesgado. Habets coincide y aclara que no propone tirar C a la basura — propone reconocer la realidad y dejar de escribir código nuevo en C cuando hay alternativa razonable. La diferencia entre mantener libcurl en C y empezar un parser nuevo en C es enorme.

💭 Clave: el argumento no es contra C como artefacto histórico. Es contra C como elección por defecto para código nuevo en 2026, cuando existen Rust, Zig y otras alternativas con sistemas de tipos modernos.

Tercero, el comité C avanza. C23 fijó varios UB históricos: la representación de complemento a dos para enteros con signo ya es obligatoria, por ejemplo, aunque el desbordamiento sigue siendo UB. C2y y futuros estándares prometen más fixes. Habets reconoce el progreso pero apunta que el ritmo es de décadas, no de releases. Para un proyecto greenfield iniciado hoy, esperar a que el comité elimine el UB no es un plan viable.

Qué hacer hoy si trabajás con C

Para desarrolladores en LATAM trabajando con C/C++ activo, el ensayo sugiere acciones concretas que se pueden implementar esta semana:

  • Compilar siempre con -Wall -Wextra -Wpedantic y al menos un sanitizer activo en CI.- Activar UBSan en tests, no solo ASan. UBSan captura los casos sutiles de cast, alignment y overflow que ASan no ve.- Usar -fno-strict-aliasing y -fwrapv como red de seguridad mientras se migra código heredado. No son una cura, pero reducen la superficie.- Evaluar Rust o Zig para módulos nuevos donde el rendimiento importe pero la corrección sea crítica. Un parser de protocolos, un encoder de media, un componente de red — todos candidatos.- Documentar cada cast de puntero que cruce tipos, porque casi siempre es UB y necesita auditoría manual.- Adoptar std::span y std::string_view en C++20 en lugar de punteros crudos cuando sea posible. Reducen oportunidades de error pero no eliminan UB.

Qué sigue

El debate no se cerrará con un post de blog. C va a seguir existiendo y se va a seguir escribiendo. Linux, glibc, OpenSSL, SQLite, Postgres, Redis, FFmpeg, el kernel de cada sistema operativo serio: todo eso es C o C++ y va a seguir siéndolo por décadas.

Lo que cambia es la dirección del flujo de código nuevo. Cuando una startup de Silicon Valley o de São Paulo elige un lenguaje para su backend en 2026, la decisión rara vez es C++. Cuando Microsoft anuncia que va a portar partes del kernel de Windows a Rust, el mensaje no es para los que mantienen NTFS. Es para los que van a escribir el próximo subsistema.

La industria se mueve y los lenguajes nuevos absorben las lecciones de cuatro décadas. Que un ingeniero de Google con tres décadas de C++ ratifique públicamente esta lección no cambia el rumbo, pero le da peso a una conversación que en LATAM apenas empieza.

📖 Resumen en Telegram: Ver resumen

Preguntas frecuentes

¿Qué es exactamente el comportamiento indefinido en C?

Es una categoría del estándar de C que describe operaciones para las cuales el lenguaje no especifica qué debe ocurrir. El compilador puede asumir que tu código nunca las invoca, y optimizar bajo esa premisa. El resultado puede ser un binario que funciona, uno que crashea o uno que produce resultados arbitrarios — todo es válido según el estándar.

¿Desactivar optimizaciones evita el comportamiento indefinido?

No. UB es una propiedad semántica del lenguaje, no una consecuencia de optimizar. Incluso con -O0, el compilador puede generar código que rompe con UB, especialmente en arquitecturas exóticas o cuando el backend traduce la representación intermedia a assembly.

¿Sirve UBSan para encontrar todos los casos?

UBSan encuentra muchos casos de UB pero no todos, y solo los detecta cuando se ejecutan en runtime con la instrumentación activa. Es una herramienta excelente en CI y en QA, pero no garantiza ausencia de UB en código que no se ejercitó durante los tests.

¿Rust elimina completamente el problema?

Rust elimina los UB de seguridad de memoria en código safe, que es la mayoría del código que escribís. El código marcado unsafe puede tener UB y requiere auditoría manual. La diferencia es que en Rust el código unsafe es una porción pequeña y localizada, mientras que en C todo el código es implícitamente unsafe.

¿Debería reescribir mi proyecto C en Rust?

Probablemente no. Reescribir es caro y arriesgado. Lo que sí tiene sentido es no empezar proyectos nuevos en C cuando hay alternativa razonable, y considerar Rust para módulos nuevos dentro de un proyecto existente. Bun, por ejemplo, mantiene Zig para el core y agrega Rust en módulos nuevos.

¿C23 resolvió los problemas históricos?

Parcialmente. C23 mandató representación de complemento a dos para enteros con signo, agregó atributos de seguridad y limpió casos puntuales. Pero el grueso del UB sigue presente: alignment de punteros, casts entre tipos incompatibles, desbordamiento con signo, accesos fuera de objeto y muchos más.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Your AI Conversations Are a Privacy Blind Spot. Fix This Now.

You wouldn’t email your codebase to a stranger. But you might be sending it to an AI tool without realizing it.

I’ve been using AI tools for over a year now — ChatGPT, Claude, Gemini, you name it. I’ve discussed product architecture, reviewed sensitive client requirements, debugged proprietary code. And I realized something uncomfortable:

I have no idea where these conversations go after I close the tab.

Not the company. The infrastructure. The backups. The training data opt-outs. The third-party analytics embedded in the web app. I clicked “I agree” on the terms of service for five different AI platforms and never read any of them.

That’s not a flex. That’s negligence.

The Export-Local Pattern

Here’s what I started doing after the realization hit: export every important conversation and store it locally.

Not for organization. For ownership.

When a conversation contains:

  • Code snippets from my projects
  • Business strategy discussions
  • Client-specific analysis
  • Anything I wouldn’t want in a data breach

…I export it immediately. Markdown, PDF, whatever format works. Stored in an encrypted folder on my machine.

The reasoning is simple: once it’s on their servers, I don’t control it. Once it’s on my disk, I do.

The Multi-Platform Problem Makes It Worse

Most people use multiple AI tools. That’s five different privacy policies, five different data retention practices, five different potential breach surfaces. And you can’t even see all your data in one place because each platform locks you into their UI.

The export tool I use is XWX AI Chat Exporter — it works across all 5 major platforms from one extension, which means I have a single workflow for data sovereignty. Export to PDF, Markdown, or JSON, and it all lands on my machine where I control it.

Again, I’m not saying you need this specific tool. I’m saying you need a strategy.

A Simple Privacy Audit

Here’s a 5-minute exercise I recommend:

  1. Open your AI tool settings and check “Data Controls” or “Privacy”
  2. See if there’s an opt-out for training data usage (most have one, most people don’t use it)
  3. Check how long they retain your conversations
  4. Export anything sensitive you’ve discussed in the last month
  5. Move it to local storage

Do this for each AI tool you use. It takes 25 minutes total and you’ll sleep better.

The Bigger Picture

AI companies aren’t malicious. But they’re businesses with different incentives than yours. Your conversations are valuable data. Their privacy policies are designed to protect their liability, not your interests.

Taking ownership of your AI conversation data isn’t about paranoia. It’s about the same instinct that makes you back up your code, encrypt your passwords, and read contracts before signing.

Your thinking process deserves the same protection as your source code.

Have you ever thought about where your AI conversations go? What’s your approach to AI privacy?

Material Nova VS Code Theme

meta_title: “Material Nova VS Code Theme: 12-Variant, WCAG AAA Color System for Serious Developers”
meta_description: “Material Nova is a premium VS Code theme with 12 WCAG AAA-compliant variants — OLED Dark, Palenight, Ocean, Deepforest & more. Built to eliminate eye strain and boost code readability. Try it free.”

reading_time: “~11 min read”

Why I Stopped Tolerating Bad Themes and Built Material Nova for VS Code

After 8+ hours staring at a screen, 73% of developers report chronic eye strain — and your color theme is likely making it worse.

Most VS Code themes were designed to look good in screenshots. Material Nova was engineered to feel good at 2 AM during a production incident. There’s a meaningful difference, and once you experience it, you can’t go back.

This post breaks down exactly what makes Material Nova a technically superior choice — from its WCAG AAA contrast architecture to its semantic token system — and why switching themes isn’t a cosmetic decision. It’s a performance decision.

What Is the Material Nova VS Code Theme?

Material Nova is a premium Visual Studio Code color theme package built on Google’s Material Design color system. It ships with 12 carefully engineered variants, each targeting a distinct lighting condition, display type, or developer preference.

The package is not a port or a reskin of an existing theme. Every color value, contrast ratio, and semantic token assignment was hand-tuned from scratch — in JSON, no tooling abstractions — with one constraint above all others: zero cognitive fatigue.

It’s available on the VS Code Marketplace and the source lives at GitHub.

[INTERNAL LINK: VS Code productivity setup guide]

The Problem With Most Developer Themes

Before understanding what Material Nova does differently, it helps to understand what the alternatives get wrong.

One Dark Pro is the most downloaded theme in the Marketplace. It has beautiful aesthetics. But its string-to-keyword contrast delta is narrow — on lower-gamma displays, variables and function names blur together. Your brain compensates. Over hours, that compensation becomes fatigue.

Community Material Theme has been the go-to Google Material port for years. But it ships a single-dark-mode mindset into a world where developers work in sunny offices, tinted monitors, and ultra-bright MacBook displays. One background doesn’t scale.

Tokyo Night leans heavily purple. Gorgeous at first. But purple-dominant palettes suppress melatonin inconsistently across monitor color profiles. On wide-gamut panels (P3), it oversaturates.

The pattern: themes optimized for aesthetics, not ergonomics. Material Nova inverts that priority.

12 Variants, One Architectural Decision

Here’s the full variant matrix with the technical reasoning behind each:

Variant Background Primary Use Case
Default #263238 Classic Material baseline — balanced gray-green, ideal for 6500K monitors
Darker #212121 Matte near-black for distraction-free deep work
Palenight #292D3E Violet-shifted cosmic palette, AMOLED-friendly
Ocean #0F111A Deep navy, maximizes perceived contrast on IPS panels
Deepforest #141F1D Organic green-black, clinically the most eye-relaxing hue range
Lighter #FAFAFA True light mode, calibrated for high-ambient environments
Default HC #000000 High Contrast companion — pure black for accessibility compliance
Darker HC #000000 Maximum contrast ratio baseline for visually impaired users
Palenight HC #000000 HC variant with violet accent system intact
Ocean HC #000000 HC variant with full blue-range semantic tokens
Deepforest HC #000000 HC variant retaining full green token assignments
Lighter HC #FFFFFF High Contrast light — meets WCAG AAA on every token

Every dark variant ships with a High Contrast (HC) twin. This isn’t a marketing checkbox. The HC variants are built for developers with color vision deficiency, users on low-quality TN panels, and anyone running VS Code through a remote desktop connection where gamma shifts unpredictably.

[EXTERNAL LINK: WCAG 2.1 Contrast Guidelines — W3C]

WCAG AAA Compliance: What It Actually Means in a Code Editor

WCAG AAA requires a contrast ratio of 7:1 between foreground text and background. Most themes hit WCAG AA (4.5:1) at best. Material Nova hits AAA across all critical token categories.

Here’s what that means in practice:

  • Keywords (if, for, return) must be immediately distinguishable from identifiers (variable names, function names) at a glance
  • String literals must pop against background without requiring the eye to refocus
  • Comments need to be readable but recessive — low enough not to compete with active code, high enough to scan when needed
  • Error and warning markers must cut through the syntax color layer without creating visual alarm fatigue

Achieving all four simultaneously is a constraint satisfaction problem. Raise keyword contrast too high, it competes with operators. Push comment saturation too low, it disappears on gamma-shifted displays.

The Material Nova color assignments were validated against a WCAG AAA checker on every variant before release. Not spot-checked — every token pair.

Semantic Syntax Highlighting: Precision That Standard Themes Skip

VS Code supports two highlighting layers: TextMate grammar scopes (legacy) and Semantic tokens (modern, language-server-powered). Most themes configure TextMate and call it done.

Material Nova configures both — and its semantic token rules are the technical differentiator.

What Semantic Tokens Enable

When your language server (TypeScript, Pylance, rust-analyzer, etc.) emits semantic tokens, VS Code knows the meaning of a token, not just its syntactic category. This enables distinctions that TextMate can’t make:

  • A string in a template literal vs. a string used as a dictionary key
  • A variable that is a readonly class property vs. a mutable local
  • A function that is async vs. synchronous
  • A parameter that is destructured vs. passed directly

Standard themes collapse these into single color assignments. Material Nova assigns distinct visual weights so that code structure is readable without running the logic mentally.

For TypeScript/TSX and React, this matters enormously. A codebase with hooks, HOCs, generics, and conditional types becomes a visual tangle in most themes. Material Nova’s semantic rules surface the structural hierarchy before your eyes process the identifiers.

For Python, class decorators, f-string interpolations, and type annotation tokens are individually scoped — so @dataclass doesn’t look like @property which doesn’t look like @pytest.fixture.

For C#, the distinction between interface, class, struct, and enum declarations is visually encoded, not just syntactically named.

Performance Architecture: Why Zero-Overhead Matters

Theme performance is rarely discussed because most developers assume themes are inert. They’re not.

Several popular themes use JavaScript-based extensions alongside their color definitions — loading background images, rendering gradient overlays, or injecting CSS into the workbench. These add latency to editor startup, increase memory footprint, and occasionally cause flicker on window focus.

Material Nova is pure JSON. No extension activation events. No background scripts. No dynamic recalculation.

The .json declaration is loaded once at editor startup into VS Code’s internal color registry. After that, it consumes zero CPU cycles. On low-powered machines (older MacBooks, Linux dev boxes, Raspberry Pi 400s running remote VS Code), this difference is measurable.

Startup benchmark comparison (anecdotal, M1 MacBook Air):

Theme Extension Type Avg Activation Time
One Dark Pro Pure JSON ~0ms
Dracula Official Pure JSON ~0ms
Peacock (theme + extension) JS + JSON ~80-120ms
Material Nova Pure JSON ~0ms

Zero overhead isn’t a boast — it’s a consequence of deliberate architecture choices.

Recommended Configuration for Maximum Visual Fidelity

The theme performs well with VS Code defaults. But to unlock its full rendering quality, the following settings.json block pairs typography with the color system:

{
  "editor.fontFamily": "JetBrains Mono, Fira Code, monospace",
  "editor.fontSize": 14,
  "editor.lineHeight": 1.6,
  "editor.letterSpacing": 0.5,
  "editor.fontLigatures": true,
  "editor.cursorBlinking": "smooth",
  "editor.cursorSmoothCaretAnimation": "on"
}

Why Each Setting Matters

JetBrains Mono as primary font — its x-height and stem weight are calibrated for code legibility at 14px. Its ligatures (!=, =>, ===) reduce token-level visual noise.

lineHeight: 1.6 — tighter than default on many setups, but creates enough vertical rhythm that the eye tracks lines without hunting. Below 1.5 collapses lines on dense code blocks. Above 1.7 introduces excessive whitespace that breaks code chunking.

letterSpacing: 0.5 — adds half-pixel breathing room between characters. On retina displays this is imperceptible. On 1080p displays it prevents character collision in condensed fonts.

fontLigatures: true — renders multi-character operators as unified glyphs. Reduces operator scan time in dense TypeScript or arrow-heavy functional code.

cursorSmoothCaretAnimation: "on" — prevents the hard-cut cursor jump that creates visual micro-interruptions during fast typing. Smoother animation reduces cognitive interruption when switching focus to the cursor position.

This configuration was tuned on the Default and Ocean variants, but applies well across the full palette.

How to Install Material Nova

Installation takes under 30 seconds:

Option 1 — Marketplace UI:

  1. Open VS Code
  2. Ctrl+Shift+X (or Cmd+Shift+X on macOS)
  3. Search Material Nova
  4. Click Install on the dhavalkurkutiya.vsc-material-nova package
  5. Ctrl+K Ctrl+T → Select your preferred variant

Option 2 — Command Palette:

ext install dhavalkurkutiya.vsc-material-nova

Option 3 — CLI:

code --install-extension dhavalkurkutiya.vsc-material-nova

After installing, open the theme picker with Ctrl+K Ctrl+T and filter by “Material Nova” to see all 12 variants. Spend 5 minutes with Ocean or Deepforest before deciding — first impressions of dark themes are heavily influenced by your previous theme’s color memory.

Variant Selection Guide: Match Your Environment

Choosing the right variant isn’t aesthetic preference — it’s environmental optimization.

Late night, dark room: Start with Ocean (#0F111A). The deep navy absorbs ambient light without the hard contrast shock of pure OLED-black themes. If you’re on an OLED display (Samsung, LG WOLED, MacBook Pro XDR), try Palenight — its violet tones work with AMOLED subpixel geometry.

Daytime, bright office: Lighter is the only sane choice. Dark themes in daylight cause your pupils to dilate trying to process the dark background against a bright room. High ambient light environments need high-key backgrounds.

Mixed / variable lighting: Default (#263238) is the most neutral choice across lighting conditions. The gray-green background sits at the perceptual midpoint between warm office light and cool monitor backlight.

Accessibility requirement: Any HC variant. If you’re building for a shared development machine, a remote desktop environment, or working with developers who have color vision deficiency, the HC variants maintain full token semantics at maximum contrast.

FAQ

Does Material Nova support Semantic Highlighting for all major languages?

Material Nova configures semantic token rules for TypeScript, JavaScript, TSX, JSX, Python, C#, HTML, and CSS out of the box. For languages not in this list (Rust, Go, Kotlin), VS Code’s fallback to TextMate grammar scopes is fully supported — the base palette provides valid contrast on all token categories. Rust and Go support is on the active roadmap via rust-analyzer and gopls semantic token mapping.

Is there a performance difference between the 12 variants?

No. All 12 variants are static JSON declarations of identical structure. VS Code loads the active theme once at startup — there is no per-variant performance delta. The only measurable difference is load time on first theme switch, which triggers a workbench color re-render. This is a VS Code engine operation, not a theme operation, and completes in under 200ms regardless of variant.

Conclusion

A theme is not decoration. For a developer spending 8-12 hours a day in an editor, the color architecture of your environment is a cognitive infrastructure decision.

Material Nova was built to solve a real problem: themes that look great in screenshots but create visual fatigue, semantic ambiguity, and accessibility gaps in daily use. Its 12-variant system, WCAG AAA compliance, semantic token precision, and zero-overhead architecture are not feature bullet points — they’re the result of specific, validated engineering decisions.

If you’re still on a theme you installed three years ago because switching felt like unnecessary friction, that friction has a cost. It accumulates across every hour of every day you spend writing code.

→ Install Material Nova on the VS Code Marketplace and try the Ocean or Deepforest variant for one full workday. If your eyes don’t thank you, the uninstall takes one click.

Star the repo on GitHub to follow updates, new variants, and language server integrations.

How Schools in Nepal Can Improve Bus Safety and Parent Tracking

Every morning across Nepal, parents prepare their children for school based on a single piece of information: the time the bus is supposed to arrive. If the bus usually comes at 7:10, the child is dressed by 7:00 and standing at the gate by 7:08. That is the entire system. Most parents have no way to know if the bus is running on time, stuck in Kathmandu traffic, taking a different route, or already left the area.

When the bus is late, parents wait at the gate with their child, sometimes in cold weather, sometimes in rain, often without knowing whether the bus is two minutes away or has skipped the route entirely. A phone call to the school office usually returns the same answer: the office does not know either. The driver is unreachable while driving. Everyone is operating blind, every morning.

This is the gap that modern school bus systems close. Once GPS tracking and a parent app are in place, the question “where is the bus” stops requiring a phone call. Parents see the bus on a live map. They get notified when it is five minutes from their stop. They send the child out at the right moment, not too early, not too late. The change is small in technology but large in daily life.

This guide is for school administrators and transport in-charges who want to make their buses genuinely safer and trackable by parents in real time. We start with practical steps any school can take, then explain how a modern GPS tracking system fits in, what it costs, and what to look for before signing up.

Five things every Nepali school can do this term to improve bus safety

Before discussing technology, here are five operational steps any school can take, often within the same academic term, to make bus transport safer:

  1. Assign one transport in-charge with real authority. Not the principal’s secretary doing it as a side task. Someone whose primary job is buses, drivers, routes, and parent communication. Most safety failures trace back to nobody being clearly responsible.
  2. Build a written route and stop map. Every route, every stop, every assigned child, every parent contact, kept in one document the office can pull up in 30 seconds. Most schools rely on the driver’s memory, which fails on the day a substitute driver takes over.
  3. Set a clear pickup and drop window for each stop. If the bus is supposed to reach a stop between 7:05 and 7:10, parents and drivers both know. Anything outside that window triggers a phone call. This single discipline removes most parent-school conflicts about timing.
  4. Train drivers on harsh braking, idling, and turning. Bus drivers in Nepal rarely receive any formal driving review after they are hired. A 30-minute monthly conversation with the transport in-charge, based on real data, changes behaviour fast.
  5. Install GPS tracking with a parent-facing app. This is the modern enabler that makes the previous four steps actually work at scale. It also adds emergency response, route history, and audit-ready records that schools increasingly need.

The first four steps are organisational. The fifth is technical, and it is what the rest of this article focuses on, because it is where most schools either save money smartly or waste it badly.

What GPS tracking with parent access actually does

A GPS device is installed inside each bus. It records the bus location every few seconds and sends that data to a server hosted in Nepal. The server feeds three things at once:

  • A parent app on Android and iOS where each parent sees only their own child’s bus on a live map
  • An institutional dashboard for the school transport in-charge to monitor every bus at once
  • An alert engine that sends notifications for stop approach, route deviation, harsh driving, and SOS events

What makes the difference between a basic and a useful system is everything around that core: route management, stop-by-stop ETA, parent notifications, driver behaviour reports, and safety alerts. Without these, a tracker is just a moving dot on a map.

What parents specifically need from the system

Parents do not need a fleet management dashboard. They need three things, fast and reliable:

  • Live location on a phone, ideally with a map view that updates in real time
  • Notification when the bus is approaching their stop, so the child does not stand outside in cold or heat unnecessarily
  • Confirmation that the child boarded and got off, either through RFID cards or a small driver action in the app

A few extras help: a shared route view for both parents, alerts when the bus deviates from its expected path, and a clear historical view for any parent who wants to verify a specific day. NepTrack provides all of these by default on the school bus tracking page.

The features that matter to schools

School administrators need a different set of capabilities:

  • Real-time visibility over every bus in the fleet, not just one at a time
  • Driver behaviour monitoring for harsh braking, rapid acceleration, and idling, so schools can take action before an incident happens
  • Trip history retained for at least one full academic year so any parent complaint or safety review can be investigated
  • Route optimisation to reduce fuel cost and journey time
  • Centralised admin panel so the transport in-charge can see every bus on a single screen, instead of juggling separate phone calls, driver WhatsApp groups, and parent enquiries for each route

This last point is where many cheap GPS solutions fall apart. They focus only on the parent side, giving each parent a basic map view of one bus, and skip the school operations side entirely. There is no admin dashboard for the transport in-charge, no driver behaviour reports, no fleet-wide alerts. A school running 10 buses for 600 students cannot manage operations from a consumer-grade parent app.

What does school bus GPS tracking cost in Nepal

The honest range for 2026, based on what schools across Nepal are actually paying:

Fleet size Per-bus device cost Annual subscription Notes
2 to 5 buses NPR 10,000 to 15,000 Bundled first year Small private schools
6 to 15 buses NPR 10,000 to 15,000 Volume discount typical Most international schools fall here
16+ buses Custom enterprise quote Often discounted Large school chains, hostels with transport

NepTrack installation is completely free anywhere in Nepal, with no remote-area surcharge. Full pricing details are on the pricing page, and a broader 2026 cost breakdown is in our GPS tracker pricing guide.

What schools should look for before signing up

Procurement decisions are often made under time pressure, and that is exactly when bad systems get chosen. Use this checklist before signing a contract:

  1. Does the provider have a real institutional dashboard? Ask to see a live demo with multiple buses, not just one tracker.
  2. Is there a parent app available on both Android and iOS? Not just a web link sent over WhatsApp.
  3. How long is trip history retained? Less than one academic year is not enough.
  4. What is the GPS accuracy? Ask for the exact specification. Quality devices report 2.5 metre accuracy with 33 to 72 satellite channels. Cheap devices claim 5 to 10 metres and lose lock in hills, alleyways, and dense traffic.
  5. Is the data hosted in Nepal? Important for compliance and audit requirements when future government regulations arrive.
  6. Does the system support Bikram Sambat dates? Important for school office paperwork and parent reports.
  7. Is local support available in Nepali? Not an outsourced foreign call centre.
  8. Can the school cancel anytime? Avoid long-term lock-in contracts.
  9. Are SDKs and an open API available? If your school already runs its own app or website for parents, the GPS provider should offer SDKs and an open API so bus tracking can be integrated directly into your existing platform. Schools without an existing app can use the provider’s ready-made parent app instead. See NepTrack’s developer documentation for what proper SDK and API access looks like.

If a vendor cannot answer all nine clearly, that is a procurement signal in itself.

The driver behaviour question

A GPS tracker on a school bus is incomplete without driver behaviour monitoring. Three patterns matter most:

  • Harsh braking, which usually means following too closely or speeding into traffic
  • Rapid acceleration, which wastes fuel and unsettles children
  • Idling time, which wastes fuel and increases pollution near school gates

NepTrack provides automatic driver behaviour scoring out of the box. Schools can review weekly reports and take corrective action with drivers before a small habit becomes a safety incident.

Privacy and child safety

Parents often ask one question that no GPS provider mentions in their marketing: who else can see where my child is going?

This matters. A school bus tracking system should:

  • Show parents only their own child’s bus, never another route they have no business knowing
  • Restrict driver location data to authorised school staff
  • Encrypt all location traffic between device and server
  • Never sell or share location data with third parties
  • Maintain immutable audit logs of who accessed what

NepTrack follows all of these by default. Privacy is not an extra feature, it is the baseline.

SOS and emergency response

Beyond daily tracking, schools should ask what happens in a genuine emergency. The right answers:

  • An SOS button on the device, accessible to the driver and bus assistant
  • Automatic collision detection, which raises an alert if the bus experiences a sudden impact
  • Direct alert to school office and parents within seconds, not minutes
  • Escalation to higher authority if the school office does not acknowledge within the response window

NepTrack’s SOS Emergency Response covers all four. Most consumer-grade trackers cover none.

Common mistakes schools make

Patterns we see repeatedly:

  • Buying the cheapest tracker on the assumption that all GPS units are the same. They are not. Cheap devices typically have low-channel GPS receivers with 5 to 10 metre accuracy, fail to hold lock under tree cover or inside narrow Kathmandu lanes, and report positions infrequently. Quality units use 33 to 72 channel receivers with 2.5 metre accuracy, hold lock in hills and dense areas, and update every few seconds. On a school bus carrying children, the difference between a position pin landing on the right gate or on the wrong street matters.
  • Skipping the parent app to save money. The whole point of school GPS tracking is parent communication. Without an app, schools end up answering phone calls all day.
  • Ignoring driver behaviour reports for the first few months, then being surprised when an incident happens.
  • Letting the contract lapse on subscription, only to find the tracker still working but with no support when something breaks.
  • Self-installing trackers on a few buses, which voids warranties and leaves wiring exposed for drivers to disconnect.

What a good rollout looks like

For a school deploying GPS tracking for the first time:

  1. Week 1: Vendor meets with school transport in-charge to map routes, stops, and student-bus assignments.
  2. Week 2: Devices installed across the entire fleet, free of cost. Each install takes 30 to 60 minutes per bus.
  3. Week 3: Parent app rollout. Onboarding session with parents, ideally during a parent meeting day. Each parent gets installation help if needed.
  4. Week 4: First weekly driver behaviour report reviewed by transport in-charge. Adjustments made if any patterns emerge.
  5. Ongoing: Monthly review with vendor. Quarterly check on any feature requests from school staff.

A good vendor does not disappear after installation. They train, follow up, and adjust as the school’s needs change.

Why we built school bus tracking the way we did

NepTrack’s school bus tracking grew out of one observation: most GPS providers in Nepal sell trackers to fleet operators and treat school buses as a side category. We did the opposite. We built the school bus solution from the parent and school perspective first, then added the institutional dashboard on top.

That meant native Bikram Sambat dates across the entire platform, Nepali language support throughout, free installation across all of Nepal including remote areas, 5 year trip history retention, and a parent app that does one thing extremely well rather than a hundred things badly.

Compare detailed features on the school bus tracking page, or talk to us through the contact form for a no-pressure conversation about what your school needs.

Frequently asked questions

Is GPS tracking mandatory for school buses in Nepal?
Long-distance public buses already have a mandatory GPS requirement as of January 2026. School buses are not yet explicitly mandated, but the Department of Transport Management’s draft guidelines (Guidelines on Making Public Transport Technology-Equipped, Healthy, Safe, Dignified and Passenger-Friendly, 2026) propose making GPS and CCTV mandatory across all public transport. The draft has been forwarded to the Ministry of Physical Infrastructure and Transport for approval. Once approved, school buses are very likely to fall under this requirement. Most reputable schools are already installing GPS voluntarily ahead of the rule, partly because parents expect it and partly to avoid a rushed compliance scramble later.

How much does it cost to track one school bus?
NepTrack school bus tracking starts at NPR 10,000 to 15,000 per bus including the first year of service. Volume discounts apply for fleets of six or more buses. Installation is completely free anywhere in Nepal.

Can parents see only their own child’s bus?
Yes. NepTrack restricts parent app access to only the bus that their child is assigned to. Parents cannot see other routes or other children’s locations.

What if the bus driver disconnects the GPS device?
NepTrack devices send a power-cut alert the moment they lose power. The school transport in-charge is notified immediately. The device also has internal battery backup, so it keeps reporting for several hours even after disconnection.

Does it work in remote areas outside Kathmandu Valley?
Yes. NepTrack devices use multi-protocol GPS with LBS fallback for areas with weak GPS signal, common in Nepal’s hill regions. Installation is provided free anywhere in Nepal.

What happens during a network outage?
Devices store position data locally and transmit it once connectivity is restored. No data is lost during temporary outages.

How long is trip history retained?
NepTrack retains full trip history for up to 5 years. This is significantly longer than most providers, which keep only 30 to 90 days.

The honest summary

School bus GPS tracking in Nepal in 2026 is not a luxury. It is a baseline expectation for parents and a basic operational tool for schools. The technology has matured, the prices have stabilised, and the difference between a good system and a bad one is no longer about features, it is about discipline, support, and treating parents and schools as the actual users.

NepTrack starts at NPR 10,000 to 15,000 per bus with the first year of service bundled in. Free installation across Nepal. Native parent app on Android and iOS. Full Bikram Sambat support. Local Nepali support team. That is what we offer, and we are happy to walk any school through a demo before any commitment is made.

If you are a parent whose school does not yet have GPS tracking, share this guide with the school administration. If you are a school administrator evaluating options, use the eight-point checklist above and request demos from at least two providers before deciding. Bad choices in school transport are expensive to undo.

Reach us through the contact page or check our full FAQ for more details.

Why my keyboard handler caused #GP (and how I fixed it)

— A true story of debugging a 32‑bit kernel

A few weeks ago I was happily writing my own 32‑bit x86 operating system, AxonOS. Everything worked: the bootloader, protected mode, a simple “Hello” message. Then I decided to add keyboard input.

I wrote an IRQ1 handler, compiled, ran… and QEMU crashed hard with a General Protection Fault (#GP) on every key press. Triple fault. Reboot loop. Frustration.

After two hours of staring at the code, I finally understood the problem – and it wasn’t inside the handler logic. It was two stupid mov instructions.

🧠 What I wanted to do

  • Catch keyboard interrupts (IRQ1) and read scancodes from port 0x60.
  • Convert scancodes to ASCII characters and print them to the screen.
  • Simple, classic OS‑dev step.

My handler looked (mostly) correct: read scancode, skip key‑release, translate via a table, write to video memory.

💥 What went wrong

Every key press caused #GP. The processor reset. No error message, no debugging output – just a black screen.

Here’s the piece of code that broke everything:

keyboard_handler:
    pushad
    mov ax, 0x1000
    mov ds, ax
    mov ax, 0xB800
    mov es, ax
    in al, 0x60
    ...

Can you spot the error?
🔍 The investigation

In real mode (16‑bit), mov ds, ax with ax = 0x1000 would set the data segment to physical address 0x10000.
In protected mode (32‑bit), segment registers hold selectors – indexes into the GDT (Global Descriptor Table).

0x1000 is a garbage selector – it points to no valid descriptor.

0xB800 is also a garbage selector.

Loading them doesn’t trigger an immediate fault, but as soon as the CPU tries to access memory through ds or es (e.g., stosw with es), it throws #GP.

The handler looked like it should work, but the environment was completely different from real mode.
✅ How I fixed it

My GDT uses the flat memory model:

Code selector: 0x08

Data selector: 0x10 (base = 0, limit = 4 GiB)

So I changed two lines:


keyboard_handler:
    pushad
    mov ax, 0x10        ; correct data selector
    mov ds, ax
    mov es, ax
    in al, 0x60
    ...

And for video memory, I stopped using segment overrides. Instead, I write directly to the linear address 0xB8000:


mov edi, [cursor_pos]
shl edi, 1
add edi, 0xB8000
mov ah, 0x07
stosw

No more es magic. Now the handler works perfectly.
📦 Key takeaways

In protected mode, segment registers store GDT selectors, not physical addresses.

Values like 0x1000 or 0xB800 are almost always invalid and will cause #GP when accessed.

Always check your GDT and use the correct selectors (e.g. 0x08 for code, 0x10 for data).

When writing to video memory, use the linear address 0xB8000 and a flat‑mapped ds/es.

One small mistake can cost you hours of debugging. But once you understand the model, it becomes obvious.
🔗 Links & resources

Source code of AxonOS (full kernel + bootloader):
👉 github.com/Paullknya/AXonOS-kernel

My GitHub profile:
👉 github.com/Paullknya

Discuss this article on the community forum:
👉 paullknya.github.io

🚀 What’s next?

Next article I’ll show how I added ring 0/3 switching and why sudo reboot asks for a password.
If you’re interested, follow me or star the repo – it helps a lot.

Happy coding, and may your selectors always be valid.