Wednesday, June 10, 2026

Hangar Board: the token count so far

In the previous post I wrote about building Hangar Board, the flying-group app, with Codex doing the coding. Here is the usage so far:

 

Monday, June 08, 2026

Hangar Board: writing a flying group app with Codex, without looking at the code

I've been having some more spare time recently. Since I have been using coding agents at work for some time now, and in recent months have switched mainly to Codex, I wanted to see whether I could write a fully functional app from scratch without writing a single line of code and without looking at the code whatsoever.

I own a share in an airplane. Our group uses a rather old piece of software for bookings. It does its job, but... it feels very much like old software. So the idea was simple: build something more modern for our group, but do not force everyone to move at once.

For the time being, bookings in the new app work as a wrapper around the existing booking service. The old system remains the source of truth, and anyone who wants to keep using it directly can still do so. The new app just gives us a nicer, group-specific front end and adds the things around bookings that we actually care about.

Current status

The app is called Hangar Board. At this point it is no longer just a toy experiment. It is fully working.

Hangar Board bookings page with member names and aircraft registration redacted

The main pieces are:

  • Bookings - view existing bookings, make new ones, amend them, cancel them, etc. There is a compact calendar which makes availability much easier to read and detects conflicts before submitting changes.
  • Aircraft page - shows useful aircraft status in one place, including cached legacy booking-system aircraft details, serviceability, check dates, latest observed airport, latest flight, and reference speeds/limits.
  • Aircraft tracking - integration with a third-party flight-tracking service whose data sources include terrestrial and space-based ADS-B. The returned flights and track-coordinate payloads are stored in a local database so users can view tracks on a map, get basic stats, export tracks in a format that can be loaded into Google Earth, and look back beyond the most recent live response window.
  • Aviation weather - METAR and TAF for our home airfield, or any other airfield we want to query. This is another third-party integration.
  • Documents - our group documents are already in a shared Dropbox folder, and I did not want to change that. Hangar Board integrates with Dropbox so users can view or download documents, while local copies can also be used by analytics and AI features.
  • Notifications - WhatsApp and email integration for booking notifications, with users able to opt in.
  • Checklists - checklists for different stages of operating the aircraft. This is implemented as an installable, offline-capable PWA using a web app manifest and service worker caching. On an iPad or iPhone it looks and behaves close enough to a native app: select the checklist, tick off the steps, and it still works without internet access.
  • AI Hangar - a chatbot using the ChatGPT Codex backend, with some of our group documents and the POH available as context. Users can ask questions about those documents, and can also use AI to prepare or modify actual bookings through the normal booking dialogs.
  • Roles - app admin, user admin, normal user, and limited user accounts for people who only need restricted access, such as maintenance users.
  • Other small things - calendar feeds, light/dark theme support, analytics, admin health checks, logs, backups, and the usual bits that turn an experiment into something people can actually use.

Integrations

One of the most interesting parts of this experiment was that all of the third-party integration work was done by Codex. Aviation weather, aircraft tracking, maps, Dropbox, WhatsApp, email - Codex suggested which services to use, went and read the API documentation, and then wrote the integration code.

My role was basically to choose from the options it recommended. I did not read the API specs. I did not write the request/response handling. I did not wire the service layers. I just guided the product decisions and checked whether the resulting app behaved correctly.

Testing the UI

Another part that still feels slightly magical is that Codex could test the UI on devices by controlling Xcode simulators. It would start a simulator, open a browser, navigate the app, take screenshots, inspect what was wrong, adjust the UI, and repeat.

This was especially useful for the checklist PWA and iPad layouts. I did not have to manually resize windows, copy screenshots around, or describe every alignment issue. Codex could see enough of the rendered result to iterate.

Tests

Codex also wrote the test suite. There are now over 800 tests, and Codex runs them every time a code change is made. That part matters a lot, because once the app grew beyond a few simple pages, manual checking would not have been enough.

Workflow

For this work I have been exclusively using GPT-5.5 xhigh. The whole process has also been a lot of fun. Sometimes I was not even at my computer: I would use ChatGPT with remote Codex access, ask Codex to implement something, and then come back later to review the result. That still feels really cool.

Result

The app is still deliberately small-scale. It uses SQLite and a dependency-light Python web app. It probably would not scale well for some larger deployment, but that is not the problem I am solving. There are six of us. For that, it works very well.

The main goal has been achieved: I did not write a single line of code, and I did not read the code. The app is fully working and working well.

Frankly, although I did expect Codex to succeed, I also expected that I would need to intervene in small ways here and there. I thought I would need to fix some code manually, read a few awkward parts, or step in when an integration got messy. That did not happen.

That said, I do think my technical background helped. I did not write the code, and I did not read the code, but knowing how software, backends, APIs, authentication, persistence, testing, and deployment usually fit together made it much easier to ask the right questions, judge trade-offs, and notice when something needed another iteration.

Still, it feels like we are now very close to the point where an AI system can generate a fully working service like this for someone without that kind of technical background. Not a toy demo, but a real, useful, integrated application built around the way a small group actually works.

When I think about it, just several months ago this was not possible. And yet now it already feels natural, almost mundane, and I find myself taking it for granted.

Truly marvelous times we live in.

Tuesday, February 24, 2026

Full Dynticks Is Not "No Ticks Ever"

On a low-latency Linux host, it is easy to assume that nohz_full (full dynticks / adaptive ticks) means "this CPU will not get ticks." In practice, tick behavior depends on both idle policy and runqueue state.

This post gives a brief summary of how this works on a Rocky Linux 9.x / 5.14.x kernel, with easy-to-reproduce examples.

TL;DR

  • nohz_full + CPU isolation does not mean zero ticks in all states.
  • With idle=poll, an idle isolated CPU can still take the periodic tick (~HZ, often 1000 Hz).
  • On CFS, if an isolated CPU has exactly one runnable task, the scheduler tick can be stopped.
  • Once that CPU has 2+ runnable CFS tasks, tick dependency is re-enabled and periodic tick resumes.


Test Setup

... isolcpus=nohz,domain,managed_irq,1-63 nohz_full=1-63 rcu_nocbs=1-63 idle=poll processor.max_cstate=0 ...

Key points:
  • CPUs 1-63 are isolated/full-dynticks candidates.
  • idle=poll is enabled.
  • HZ=1000.


Observation 1: Idle Isolated CPUs Still Tick with idle=poll

When CPUs 2 and 5 were idle, this probe showed about 5000 events per CPU in 5 seconds (~1000 Hz):


bpftrace -e 'kprobe:update_process_times /cpu==5 || cpu==2/ @ticks[cpu]++; }
             interval:s:5 { print(@ticks); clear(@ticks); }'

@ticks[5]: 4999
@ticks[2]: 4999
...

Why: idle=poll forces the polling idle path, which restarts/keeps the tick while idle instead of entering deeper idle states.

Observation 2: One Spinning Task on CPU 5 Stops Scheduler Tick

A single userspace busy loop pinned to CPU 5:

taskset -c 5 sh -c 'while true; do :; done'

CPU was 100% user, and /proc/<pid>/schedstat showed:
  • field 1 (CPU runtime) increasing,
  • field 2 (runqueue wait time) not increasing,
  • field 3 (timeslice count for this task) not increasing.
(Documentation/scheduler/sched-stats.rst defines field 3 as "# of timeslices run on this cpu". In practice this is a useful proxy for "how often the task gets sliced/re-scheduled".)

Key proof that the periodic tick stopped on CPU 5 with one runnable task: update_process_times drops from ~1000 Hz to 0.

bpftrace -e 'kprobe:update_process_times /cpu == 5/ { @ticks++; }
             interval:s:5 { printf("CPU5 ticks in last 5s: %lld\n", (int64)@ticks); clear(@ticks); }'

CPU5 ticks in last 5s: 4999
CPU5 ticks in last 5s: 5000
CPU5 ticks in last 5s: 3581
CPU5 ticks in last 5s: 0
CPU5 ticks in last 5s: 0
...

Observation 3: Add a Second Spinning Task on CPU 5, Tick Comes Back

After starting a second pinned spinner on CPU 5, /proc/<pid>/schedstat for the first task started showing:
  • runqueue wait time increasing,
  • timeslice count increasing.

With the same probe still running, update_process_times on CPU 5 went back to ~1000 Hz while both were runnable.

Mechanically (CFS path): when CFS runqueue depth grows above 1 (rq->cfs.h_nr_running > 1 on this Rocky 5.14 host), the scheduler updates tick dependency and sets TICK_DEP_BIT_SCHED, which prevents full-dynticks tick suppression and kicks the CPU back into periodic tick mode. In some newer kernels/trees, you may see equivalent logic expressed with h_nr_queued instead.

This matches scheduler behavior for CFS: with more than one runnable task, periodic preemption/accounting is needed.

Why Low-Latency Systems Use idle=poll

idle=poll is used for determinism, not efficiency:
  • avoids deep C-state entry/exit latency,
  • reduces wakeup jitter variance,
  • keeps cores immediately responsive for bursty workloads.


Scheduling Class Caveats

The "0/1 runnable task => stop tick, 2+ => restart" rule is mainly CFS behavior.
  • SCHED_OTHER/CFS: 2+ runnable tasks generally require tick.
  • SCHED_FIFO: does not time-slice equal-priority tasks; one can run indefinitely until block/yield/preempted by higher-priority work. Tick may still be forced by other dependencies.
  • SCHED_RR: more than one RR task needs tick for RR semantics.
  • SCHED_DEADLINE: tick is generally required.
Also, independent tick dependencies (perf events, POSIX CPU timers, RCU, etc.) can force tick even with one runnable task.

Practical Takeaway

If you observe periodic ticks on an "isolated nohz_full" CPU, first ask:
  1. Is idle=poll enabled?
  2. Is the CPU idle right now?
  3. Does it currently have 1 runnable task or 2+?
  4. Are there extra tick dependencies active?
In other words, full dynticks is conditional behavior, not a blanket guarantee.

Tuesday, February 03, 2026

A handy ONTAP REST fallback: POST /api/private/cli with input

The /api/private/cli family is useful, but the “standard”/structured passthrough style (where the CLI maps onto /api/private/cli/<command-path> and parameters become fields/query params) only covers a subset of CLI commands and behaviours.

When that structured approach doesn’t work (or there’s simply no corresponding REST endpoint you can use), there’s a more free-form option: POST /api/private/cli with a JSON body containing an input string. In effect, you hand ONTAP a whole CLI command line to execute. The trade-off is that you typically get CLI-style text output back (not nice JSON records), but it can still be extremely handy for one-off automation and troubleshooting.

Example (raw CLI passthrough via input)
curl -u admin -k -X POST 'https://<cluster>/api/private/cli' \ -H 'accept: application/json' \ -H 'content-type: application/json' \ --data '{"input": "system node run -node * -command \"priv set diag; wafl scan speed\""}'

Enter host password for user 'admin':
{
  "output": "2 entries were acted on.\n\nNode: XXX-01\nWarning: These diagnostic commands are for use by NetApp\n         personnel only.\nWAFL scan speed is 1300\n\nNode: XXX-02\nWarning: These diagnostic commands are for use by NetApp\n         personnel only.\nWAFL scan speed is 1300\n\n"
}

btw: The CONTAP-458497 mentiones the input approach as well.