Ask HN: How to deal with shipping AI-written code you don't understand?

1 points by rzk ↗ HN
I saw this LinkedIn post from a Meta engineer and it resonated with me:

https://www.linkedin.com/posts/ali-hamie_im-an-engineer-at-meta-and-i-never-felt-share-7517317918666211328-7C8N

> I'm an engineer at Meta and I never felt this uncomfortable doing my job. I'm writing code these days and it all feels like a black box. This new standard of shipping with AI is moving so fast that if we're going to spend our time checking each line of code, it's going to take forever. That means you can end up with systems you've built where you don't truly understand how all the code works. You understand the high level, but not every detail. And maybe this is becoming the new standard. It goes against every instinct of being a software engineer. I'm used to understanding what I'm building. Now I'm trying to figure out how much I need to understand before I'm comfortable moving forward. Are other engineers feeling this too?

How are you handling this?

622 comments

[ 0.34 ms ] story [ 29.5 ms ] thread
The solution is more AI. Throw another agent in to watch your logs, then throw in another agent to watch that agent.

Quality is no longer an objective. If it breaks, rebuild it and tell it not to break.

wow, “quality is no longer an objective” is a wild idea that feels dangerously negligent.

quality is all we have, it’s what makes us different from these agents.

quality is perplexing to agents because it requires a strange loop into asking why. there’s not always a clear answer. but i think the power is in the ambiguity.

if we don’t know why (or don’t care) then what happens if we all adopt this attitude at scale?

if quality is the currency, then we can be open and transparent about the code and software that we use. I suspect that most of the software that I use is crap, and was so even before AI came along. Maybe AI can actually make this crappy software better?
With the number of software flaws we found even before AI, I don't think quality was ever job #1.
hope this is sarcasm - why would quality not matter anymore? So why did it matter before in the first place?
I’m slowly coming around to the idea that if it works, I’m good with that. I never used to personally check all my colleagues work, either, I just trusted that they’d done a good job.

That’s not to say I blindly trust an LLM - my project is an autovectorising compiler. I have it write a bazillion tests, and I have real, non-trivial programs that it compiled. I have it run a ‘gate’ before releases that takes literally hours on 5 different architectures. To say this code is tested is … masterful understatement.

I think what helps is a long planning session, going into details, working out corner cases, designing test-cases, you know work and effort. Once that’s done it’s easier to let it work through the night and do a major production feature.

I still review the code, but hand on heart I couldn’t say I’d reviewed everything, sometimes I miss something in a complicated check-in. I try to keep checkins small and contained, but sometimes that’s not easy to manage alongside the “don’t break stuff” rule as well.

So it comes down to: I’ve been working on this for 6 months or so, and so far this has worked out for me. You know, kind of like getting used to a colleague’s idiosyncrasies over a period of time.

I will say that in my experience the code quality is fairly high. Maybe that’s down to the effort put in ahead of time in the planning stages, I don’t know, but I don’t seem to suffer from the ‘slop’ that people complain of. YMMV.

I am not in META but for what I read on blind, assuming it is true, they are not spending any time in long planning sessions or in getting into details of the implementation plan at all, and they could not anymore considering how many PRs in parallel they are requested to manage
Yeah, I'm retired. The only person I have to answer to regarding progress is me. I'm a stubborn son of a bitch when it comes to choosing quality over speed...

That doesn't mean I don't like progress mind :) This morning I got the preliminary results of a 'C' front-end to the compiler (it uses clang to compile to LLVM IR, then translates to our IR, then feeds that IR into the autovectoriser as if it had parsed the code itself). So for the exact same source code I now have:

     ┌─────────────┬────────────┬──────────────────────┬──────────┐
     │  Benchmark  │ clang -O2  │ xcc --par-auto (GPU) │ Speed-up │
     ├─────────────┼────────────┼──────────────────────┼──────────┤
     │ saxpy       │ 1,546 µs   │ 726 µs               │ 2.1×     │
     ├─────────────┼────────────┼──────────────────────┼──────────┤
     │ Mandelbrot  │ 394,238 µs │ 2,110 µs             │ 187×     │
     ├─────────────┼────────────┼──────────────────────┼──────────┤
     │ nbody       │ 37,090 µs  │ 1,035 µs             │ 36×      │
     ├─────────────┼────────────┼──────────────────────┼──────────┤
     │ perlin      │ 78,016 µs  │ 1,850 µs             │ 42×      │
     ├─────────────┼────────────┼──────────────────────┼──────────┤
     │ mandel_deep │ 73.2 s     │ 55.8 ms              │ 1,312×   │
     └─────────────┴────────────┴──────────────────────┴──────────┘
Those are some pretty fine speedups, from naive C, with no alterations from the standard "C" code - it automatically finds places that can be made data-parallel, tracks the data-hazards to make sure its safe to "GPU" and writes all the kernels, boilerplate and data-stream-management if necessary. Obviously the checksums have to match, so I know it did the work correctly too :)
That is pretty adventurous, but it sounds really cool! Will it be open source?
Yep. I’d give the link but I think HN thinks I’m spamming because I’ve mentioned it too many times before in posts - hence immediate death of any submission, so I’m not mentioning it for a while to see if that tails off.

Bottom line: GPL3 for the compiler, standard library exclusions for derived works, so use it for anything but feed me back improvements to the compiler itself, and you don’t get to redistribute it as your own.

Hell yea I love that computers are getting another chance at becoming compute fabric, just using each others GPUs. Sick.
I like it! The Rust crew would love this I bet.
Awesome, this is genuinely-cool HN material. Thanks for sharing.
Upvotes don't appear publicly on HN so I'll comment to agree with this. I'm sorry if earlier information about this compiler got lost in the whirlwind. Data parallelism is a hard problem and these results look genuinely impressive so I for one would definitely be interested in hearing more about it and maybe discussing it further when there are interesting developments.
Thanks :)

Still working on some of the data-parallel recognition in LLVM IR space. In order to support both C and C++ with the same data-parallel section extraction, I moved the detection from the ‘xc source code’ phase to the IR lowering phase, meaning that everything could ‘feed in’ to the same routines. That’s caused a little friction with the slightly different ways the LLVM IR from clang and xc’s IR encode loops. The above results are all single-loop extractions and the double-loop ones are still ongoing.

The system works in 3 stages: 1) by being greedy about which loops could possibly vectorise and marking them, 2) by analysing the IR to reject loops which do pointer-aliasing, or object manipulation, or… and then 3) by measuring the fastest way these known-safe kernels could execute.

Which brings me onto one nice thing I haven’t mentioned: that the GPU choice(s) are made at runtime, not compile time, and the system automatically measures (and then persists so it doesn’t have to measure again) the fastest way to run a kernel on the current machine. It handles MSL on Apple devices, PTX on Nvidia cards, Spir-v on Windows, Linux, and Android, and WGSL in the browser. It’ll quite happily divide up the parallel task across a 3090 and a 5080 on my Windows test box for example, with the 5080 doing ~2/3 of the work, because the scheduler is opportunistic.

All without writing anything other than plain C with nary a mention of the GPU. Give me a week or two, and I’ll probably announce it on phoronix (which might then get a mention on HN if it goes down well there, and it’ll be a different url so hopefully won’t be insta-dead).

So, the real input to your autovectorizer is LLVM IR; you could soon enough support any language that has an LLVM front-end? (I say soon enough instead of immediately because I’m sure there’s some manual wiring up to do). That seems like a pretty big deal. Do Fortran next, pls. :)
Ssshhh. I like to try things before promising them :)
> Hacker News isn't interested

I wouldn't be too sure about that :)

I’m being a little cynical - I’ve submitted it twice (though the ‘C’ front end is new) and it bombed both times - the second time within seconds of submission.

I think it’s my own fault, I’ve posted the link to the compiler pages several times in what I thought were relevant posts, and I think HN thinks I’m spamming. So I’m not posting the home page for a while and we’ll see if a future submission “takes”

might be worth firing an email to hn@ycombinator.com. they should be able to tell you why your submission was auto-killed, and might work with you to ensure your next submission isnt.
I actually did email the second time because it was clearly an automated response, but I didn’t hear anything back. Not too surprising, Dan is a very busy guy, and he doesn’t know me from Adam :)
I had to ask my LLM some questions to even understand what you are working on, very cool!

For anyone wondering, the thing lives at https://compile-xc.org/ ;)

sounds very cool - is this basically "C" support for cuTile? :)
Well, the end product might be similar, but the approach is a bit different. 'xc' is a language in its own right, and it can be compiled (with xcc) to both the CPU and GPU (and I have plans to make it work on a Zynq too, a sort of HLS).

The C front-end I'm currently working on is something that looks at the LLVM IR that clang produces and converts it to xc-IR, then runs the data-parallel extraction through the same routines as the original xc language. Consider it to be a 'Y' branch on the inputs - you can feed it LLVM IR or xc source code, they both get convered to xc-IR and from then onwards it's all the same.

So yes, you get tiled work-units, overlapping in time between compute/data-transport and tuned so that actually works, and an opportunistic scheduler that sends them to whatever worker can handle it at the instant of being sent. That opportunistic nature means the faster cards (eg my 5080) will naturally get more work, because the scheduler just keeps all cards busy. The divide between the 5080 and the 3090 in my Windows test box is about 2:1.

But the scheduler is also aware of networked GPUs - my M4 max has a pretty fast GPU, but it's connected to my wife's M3 Max by thunderbolt, and I measured a 37 Gbit/sec connection, so my app can send data to be processed over that very fast link and get results back. If you have a 10GBit nic, then it will work pretty well too, if your nic is "only" 1Gbit then modern GPU speed means that unless your work-units are really computationally intensive, it'll be faster to do it locally - and xcd will work that out at runtime and adopt that faster approach.

so the workers understand cross worker topology? Do they always just see individual tiles? what if there is a dependency - eg I'm streaming data and want to compute something later on an earlier computation? How would that get distributed?
There are three phases in auto-extraction...

- Acquisition: Greedy annotation of any possible data-parallel opportunities. This was originally done at the source-level when it was just 'xc'. For the C front end, it's been moved into the IR level, where the xc version will migrate to soon as well.

- Validation: Always done at the IR level, here we can check that there are no data-hazards, that a variable isn't being read from and written to in the same loop for example, or any aliased pointers, etc. etc. Anything that doesn't meet the cut has its annotation stripped, and we fall back to CPU, though it may still be vectorised on the CPU.

- Runtime measurement of which method is fastest, on the hardware that's actually running the code, with persistence to the local machines settings (defaults on a Mac, registry on Windows,...). If the routine passed muster during validation, it's guaranteed to be tile-able, and splittable over multiple GPUs in the system, or even networked co-operating machines (or Macs over Thunderbolt connections).

So, yes, the workers only see a tile of data, and there can't be a dependency within that tile, but xc allows trivial data-parallel annotation of loops. Here's a full-and-complete Mandelbrot that will run on a GPU:

     #use "Stdio.xc"
     #use "Par.xc"
   
     #define W 2048
   
     i32 main(void)
     {
         u32 total = 0;
         par mandel :reduce(+ total)
             {
             for (u32 i in 0..W * W)
                 {
                 float cx = (float)(i % W) * (3.0f / W) - 2.0f;
                 float cy = (float)(i / W) * (3.0f / W) - 1.5f;
                 float x = 0.0f;
                 float y = 0.0f;
                 u32 k = 0;
                 while (k < 256 && x * x + y * y < 4.0f)
                     {
                     float xt = x * x - y * y + cx;
                     y = 2.0f * x * y + cy;
                     x = xt;
                     k = k + 1;
                     }
                 total = total + k;
                 }
             }
         printf("%u iterations\n", total);
         return 0;
     }
The system caches memory as it can:

- Discrete GPU: a GPU card (Vulkan or Cuda) keeps resident any array the program hasn't written since the last upload. Running a block again that uses that same data uploads nothing.

- Remote: a remote xcd keeps every slice and kernel it has been sent, so a repeat run sends hashes, not data. Same story as above for a discrete GPU managed via xcd

- Metal and integrated Vulkan: simd: memory isn't copied at all.

...so if you want something that uses a buffer from a previous data-parallel compute, have 2 'par' sections, and share the buffer. Each 'par' section will still run serially (en-masse) by default, but you ought to be able to ping-pong between a pair of them. I haven't yet run such a thing on the CFE :)

More specifically:

- Within a block, iterations must be independent. A loop where each iteration reads the one before (b[i] = b[i-1] + 3) stays an ordinary loop.

- Reductions are the one cross-tile dependency it handles. Each tile, on whatever device, produces a partial sum, min or max, and the runtime combines them. That's what the :reduce(+ total) does above.

- The scheduler sees the topology, not the workers. It splits a block into tiles and hands them out by each device's measured speed, local or remote. Each worker sees only its tiles plus the slices of input those tiles read.

- For streaming, two blocks in sequence works. Each block finishes on every device before the next statement runs, so the second can safely read the first's output. With CUDA residency, data the CPU hasn't touched in between isn't re-sent.

One thing I'm not trying to do is cover all possible cases automatically - that way lies unbounded complexity, and I'm a fan of simple code working well with low cognitive...

I appreciate the time you took to reply to me.

Thank you!

I think your project is both cool and potentially very useful and I'm glad you mentioned it on HN! Having said that, your numbers jumped out a bit at me - the Mandelbrot and nbody/perlin numbers are in the ballpark of what I'd expect for a "random high end consumer CPU" -> 3090 transition, roughly, but the saxpy seems way off. A saxpy kernel is both trivial to write optimally and trivially memory bound virtually everywhere, so between a 3090 and a dual channel DDR5-6000 system there should pretty much always be a ~10x difference. Any idea what's going on there?
Darn. Good catch.

I was all primed to give an explanation about some of the internals of the language, and after checking ... that's not whats going on here. What I thought was happening (and why I wasn't overly surprised by the saxpy results) was this:

- The above data is actually from my M4 Max MBP, not the PC/3090/5080

- xc has the concept of memory that is sufficiently aligned that it can be used directly with GPU data in a unified memory model. You just annotate the pointer with 'simd:', thus:

   simd:u8* ptr = new u8[4096]
And what I had assumed was happening was that the C front-end was annotating its buffer correctly, that the kernel didn't need to do any buffer-copying (which is what generally slows down saxpy) and that the GPU was just ripping through the data in-place and handing back control to the CPU.

That ... turns out not to be the case [grin]. The CFE is not marking buffers that get allocated on the heap with the simd: annotation (this is a bug, I will fix it), so you are absolutely correct and the numbers don't stand up for saxpy. Here's a more in-depth table for saxpy, and it shows where the error crept in:

     ┌───────────────────────────────────┬───────────┐
     │   saxpy, M4 Max (C, --par-auto)   │   Time    │
     ├───────────────────────────────────┼───────────┤
     │ clang -O2, one thread             │ 1,398 µs  │
     ├───────────────────────────────────┼───────────┤
     │ xcc, all CPU threads              │ 518 µs    │
     ├───────────────────────────────────┼───────────┤
     │ xcc, auto (picks all CPU threads) │ 593 µs    │
     ├───────────────────────────────────┼───────────┤
     │ xcc, GPU                          │ 11,240 µs │
     └───────────────────────────────────┴───────────┘
In fact the GPU-run's own report shows where the GPU time goes. Each saxpy pass takes about 0.8ms on the GPU itself, but just over 11ms end to end. Almost all of the difference is copying the two 64 MB arrays to and from the GPU-aligned buffer on every run. The 2.1× over clang is just memory bandwidth: one core can't saturate the M4 Max's memory bus and 16 can.

The 'auto' mode (the default unless you pin a kernel as must-use-this-execution-style) measures a few iterations of the work-tiles at runtime, and picks the fastest to run the full job with on the local hardware.

Bottom line: what is actually happening here is that the 'auto' mode (also in the original table header) is doing its measurement, and picking the fastest route - which is the "all CPU threads" mode. You can see the cost (I always run these tests after clearing out the persisted previous run, so there's always the measurement-cost in the 'auto' figure). What was misleading was the "(GPU)" in the original table header, I have to apologise for that.

Having said that, I'm glad you asked - in this case I don't think it'd make an actual difference (multi-threaded CPU still beats the non-buffer-transfer GPU) but potentially an integrated GPU (Mac or one of the "AI" Ryzens perhaps) might benefit from another, very slightly more complex compute, now that I know the automatic memory-alignment isn't working properly :)

Glad my nitpick turned out to be useful!

Yeah, for an M4 Max I'd expect 1.5-2x (the CPU can only use about half the total bandwidth, roughly, so the GPU should still win if you discount initialization), though if memory serves the Metal API needed some special flag set in addition to the basic alignment requirement in order to actually directly use the same buffer without extra staging, at least when I last used it.

More generally, if your language builds a unified memory abstraction on top of systems that have physically heterogenous memory, make sure the cost model doesn't just include the nominal bandwidth differences (or asymptotic ones, if you do live profiling), but also the overhead of any extra staging buffers you use (explicitly or not, drivers and runtimes are not generally shy of allocating behind your back).

Yep, Metal has a 'noCopy' initialisation, otherwise it'll do a copy to aligned data first.

The costs are basically latency-time-based (time to prepare the system for execution) coupled with execution-time-based, rignt now. The thing is they're done by measurement, not prediction, so even if I manage to screw up the setup and do something non-optimally, that measurement is still a valid discriminant for the system it's actually running on.

I do actually update the network latency timings on an ongoing basis - there's a heartbeat that sends back info on a periodic basis, and the error-condition mechanism has to cope with a node falling off the network etc.

And again, thanks for the insight :)

Do you think they are actually shipping anything useful any faster? Muse is pretty impressive. I’m not seeing radical progress otherwise
> I’m slowly coming around to the idea that if it works, I’m good with that.

A.k.a. "The concrete didn't crumble when I hit it with a hammer."

Process quality has been a thing for ages because it is essential.

I mean, honestly ? Yeah.

Except it’s about 1000 “hammers” (aka tests), x 9 different architectures, x 2 different (a reference and a production) variants, and x 3 different optimisation levels. Every time I find something that slipped through, it gets at least one test written to cover it in the future. My main time-sink is the hours it takes to run the ‘gate’ before a release across the test-farm. And that’s after I’ve been playing with it myself for a while, pushing corner-cases through whatever I’ve been working on.

>1000 “hammers”

And who decides what hammer to use, or do you even know what hammer is used? 1000 toy hammers from walmart or temu don't mean anything.

Me. That would be me.

Based on a solid 6 months or so of testing, probing for weaknesses, and building a test to keep the system on the straight and narrow after finding a problem.

do I think the compiler is perfect ? No. No I don't. Is it as good as I can make it ? Why yes, yes it is.

Are we speaking of the same industry? The "ship fast and break things" one? The one that has traded everything from performances to security in exchange for "better developer experience" (aka "easy" aka "cheap") ??
I would not tar the whole industry with Zuckerberg's brush.
Yes, compilers are a good candidate for verification through thorough testing. If a comprehensive test suite passes, reviewing every line of code isn’t so important.
That's why Anthropics C compiler worked so well; because they had gcc's huge repository of tests to lean on. Oh wait...
Yeah, their ccc had no testsuite at all. I ran it through my testsuites and it failed miserably. plus they failed to look at any PR. At least their agent could look at the PR's.
(comment deleted)
See the first paragraph here: https://alexgaynor.net/2026/sep/07/is-it-correct/

If your definition of "works" includes "doesn't have vulnerabilities", you're going to need to do more than just poke at it and add tests.

How does writing the code by hand solve that? If anything it’s less likely to be secure, because you don’t contain the sum of all human knowledge.
What helps solve it is understanding the code deeply and thinking about it.

It's not perfect, but vibe coding means you don't even get that.

Nothing is perfect. [1] is an issue I found that was 20 years old in gmime, no LLMs involved...

Vibe-coding actually might be the absolute best way to find vulnerabilities because the AI knows a lot more about them than you or I do, it can pattern-match against known anti-patterns, will do exhaustive searches without flagging halfway through, and is kept up-to-date by its ongoing weight-maintenance about new types of vulnerability.

What you have prompted me to do is add a pass to the 'make a release' script to ask it to search for vulnerabilities and report them, then put any found ones on the TODO list for the next pass.

1: https://github.com/jstedfast/gmime/issues/183

Very similar here. For internal projects the test is - if it works, unit and integration tests were created, and it follows our architecture correctly.
> never used to personally check all my colleagues work, either,

Jesus F Christ! No wonder most software lately (~decade) has been shit. Have y'all not heard of code review? Or did you think it just for show...you sit on a change for a week and then press "Accept" without reading it?

My dear fellow, the statement was that I never used to personally check all my colleagues work. Of course I took part in code review (but then, that’s pretty much the same as me reviewing the LLM code, no ?) The statement is that I personally did not review every line of code in the repo. Some of it was authored, reviewed and committed by others.

FWIW I was a software engineer for the last 35 years or so. Amongst other claims to fame, pretty much all Apple devices on the planet (those actively running Apple OS’s, anyway) are running my code, within AuthKit in fact, and one code review was insufficient by policy when making changes. Despite all that, I’d be lying if I told you I thought there were 0 bugs in the actually-rather-complicated repo… Maybe these days, running it by an LLM would be a good idea…

I had same problem, but after a while I just accepted that is inevitable
"This code is really ugly."

That sucks. Ship it.

[flagged]
Not sure about that. This was structurally the same before AI: the author of the code wrote the tests. The tests still helped.
The author of the code was physically touching the internals and potentially intuiting obvious shortcomings as they worked on it. That very crucial part is now gone.
I like fault injection (fuzzing, chaos), and mutation testing. I think introducing these helps the AI write better tests and helps having some confidence in the tests that are written.
You should be using two (or even three) different LLMs. For example: Claude to do the coding, and Codex to do an adversarial review. This can go back and forth for several iterations before converging on an acceptably "correct" version of the code...

Occasionally, Claude may not be able to fix the issue – and it may turn out that Codex can, because it has a different approach.

Either way, at least two LLMs is the only way to get (mostly) reliable code.

Claude, ChatGPT, Google Gemini, then Replit...
You can automate this entire orchestration with zeroshot.sh, mentioning it since it is relevant here.
We split them. The agent writing the code never writes the checks it's judged by, and those separate checks caught way more than its own tests did.
Yes, this is exactly the problem that used to worry me. Most of my current repo is written by Claude, and I need to have a method to tests the tests.

I've started using Stryker. We have a CI job that mutates the code before running the tests. In the first run, it has returned hundreds of code changes confirming that the tests are biased. I haven't yet fixed those issues, but for now i am banking on this approach.

It feels like another layer of "conceptual compression". I'm not sure who/when that phrase came around but in context of Rails is where I heard it and its things like ActiveRecord or really any ORM. You aren't writing the SQL directly, something else is and for the most part it works. Then it doesn't work or your database grows too much and you need to optimize table structures with indexes, etc.

My point is if you don't understand the fundamentals of the underlying code (SQL in the example above), you're more likely to have issues in the future. Or maybe not if that app doesn't reach a usage pattern or complexity where the nuances under the hood matter.

It really depends on what you're shipping. Meta ships social media apps where in the grand scheme of things if something is broken software wise it largely doesn't matter. What's the worst case? lost ad revenue? Users can't do things? If that were software for MRI machines or aerospace where software issues/bugs can result in lives lost then it matters much more in understanding what is going on at all levels of the software and potentially hardware.

I spend time to add extra observability, cross-checking and alerting at runtime to offset the risk.
(comment deleted)
It's Meta. Shipping crap is par for the course. You're fine.
Product doesn’t care. And most of us will get replaced by agents. I guess around 30% of the engineering workforce will keep their jobs which will be mainly to keep the product up and running, doing whatever it takes (so using agents to fix stuff basically).

The ones who get to keep their jobs will be rebranded as Maintenance engineers with a lower salary. VPs and Heads of will keep their cozy job (bc there are not so many of them), and they are not that dumb to fire themselves.

Code understanding doesn’t matter anymore (or won’t matter soon)

Generally speaking I brain storm the feature first and produce a dev plan. This includes asking it to reuse code/libraries we’ve already got! After it’s gone off ah done its thing we go back over and do a QA on what it’s built, why that approach, what can we do better etc etc?
I'm currently working on a combined Aero sim/CAD/CAM software for designing, simulating, and performing DFM optimization specifically for Group 1 to 3 winged UAVs.

What I do is ask Opus/Gemini to explain the parts I don't understand. And I do some of my own research to verify accuracy of the explanations.

In addition:

* Every new feature has a Feature Design document which I review, modify, and approve. And a corresponding Implementation Plan which I also review and approve.

* Every fix has a RCA document and fix Implementation Doc which I review, modify, and approve.

* There's a global high level Architecture and Design document with additional sub component design documents which are always kept in synch after a new feature or bug fix is done. Synching it is always a step in the Implementation plan.

This does adds some overhead but it allows me to have have check point gates. This has often allowed me to catch misunderstandings and align properly before any code gets edited by the AI agent.

Having the above docs also helps when it comes to referencing them as context to properly ground new features or bug fixes.

[flagged]
> How to deal with shipping AI-written code you don't understand?

Stop shipping code you don't understand.

Is there really any other solution? You can either ignore it and pretend like it's the new era of software like half the other commenters are, or you can sit down and understand the code and what you're shipping.

I wouldn't be surprised if the meta person has a gun to their head to increasingly keep shipping like a maniac (how decade-long bugs remain unfixed don't ask me), so this advice may not be very useful.
We just don't do that, I don't know why people talk like that is a given

Is it a company culture thing or a domain specific thing?

I can think of so many times this has happened before. People getting used to MVC, Dependency Injection, ORMs. Sometimes the code is so large you only work on a part of it, so you have to get used to a lot of not knowing.

So how do we build confidence anyhow when we can't know everything?

I think first, standard checks - does it compile? What do static analysis tools say - code quality, passes SAST analysis? Linters? Style checkers?

Do the tests run? I'm not comfortable with the tests I write let alone the AI.. by adding fault injection (fuzzing, chaos), and mutation testing, I have a better degree of confidence in the tests and code.

Spend even more time getting to know the product instead. Maybe I don't know the code as well as I used to, but I know the product backwards and forwards. That helps me have some new confidence that I didn't really have before. Knowing the greater whole.

Your comment made me think that people have been happily importing hundreds to thousands of dependencies into their projects that were written by some random guys on the internet, but they've just now started to become concerned they don't understand the code they're shipping.
exactly right but think about it a bit, the responsibility is kinda off shored to the library owner and the library is used by many and battletested meanwhile

with ai the only one that use that code is only him and the reponsibility is still on him

Yes this is essentially my take I posted. We have been working at various levels of abstraction for a long time using framework, packages, and even programming languages themselves.
I'll take a look at your post. I think it's really good to point out that abstraction is something that we have sometimes accepted / sometimes struggled with our entire careers (lives?)

I think the real question here is how we rebuild some confidence and perhaps remove some fear.

Something that made MVC, dependency injection, and ORMs more palatable is that there is typically a well-structured interface between "our code" and "their code". The current state of agentic dev rarely has AI doing its thing behind some abstraction layer. It's really more like a new coworker that needs to be steered toward team norms very quickly.

I think the main challenge is pressure for more productivity with AI. I often go back and forth about if I am scrutinizing too much (and not being as productive as I could be) or not enough (and shipping slop). Building the additional QA harnesses and feedback loops for agents seems like the best known path forward to accelerate with some safety, but it is still pretty challenging to context-fiy all of the nuanced judgements a seasoned dev team makes.

Im currently building a ui framework for unreal. I dont enjoy ui work so I wanted a way to have to write less ui.

For me I dont look too closely at the implementation but I look at the api where if a human was using my framework how is the code they'd have to write and engage look at. Then I also do plenty of performance against a comparable target in my case slate (default unreal) and keep overhead minimal.

So good performance and reasonable public facing api are the 2 main pieces im focused on. Less with the internals.

Care about the seams and interaction and the things you can measure or count.

This is how every product manager feels every day.

If you're an LLM prompter shipping products, you're outputs are more akin to a product managers, not an software engineer's.

What nonsense! If a product manager ships something that breaks then the software engineer who submitted the code gets the blame!

Software engineers are always responsible for their code except now they have to be responsible for Claude's code

Oh, don't worry. Someone is also blaming the product manager!
Software engineers are always responsible for their code except now they have to be responsible for Claude's code

Which is - of course - absurd. Accepting responsibility without demanding the corresponding authority is a fool's game.

I have never been much of an advocate for unionisation in the software development industry. I didn't think it was necessary to protect the interests of the workers because for most of the industry's history the workers have largely been able to stand up for themselves anyway. Likewise I've never been a big believer in accreditation and licensing practitioners in our field - because who is qualified to judge someone else worthy in such a young industry with so little hard data about what works or doesn't work? I find my views on these subjects are evolving now.

It's not about protecting jobs if they truly aren't needed any more or protecting people working in the industry who refuse to move with the times. The Luddite philosophy isn't going to get us anywhere here. And besides - if AI is as effective as its advocates have been claiming then there should soon be more demand for capable developers to take advantage of the new opportunities. (The Jevons Paradox is relevant here.) But it is about protecting standards. If the tools aren't doing a good enough job then expecting professional developers to ship the code produced by those tools even though they know it's junk is something that needs to be challenged. And if developers are being expected to ship anything important without having the slightest idea whether it even works then that's even worse.

We had a lot of silliness going on when the very low interest rates and huge amounts of VC funding were propping up ideas and practices that would never have survived otherwise. Today we have a different kind of funny money distorting the industry but it's still propping up ideas and practices that we wouldn't normally support or perhaps even tolerate. And just like before no individual is going to be able to hold back the tide and it's going to have to be a collective effort to push back - hence my evolving views on topics like unionisation and accreditation. We've all seen the drop in quality across much of the industry and we all know why it's really happening because we seen how the sausage is now being made.

Most product managers are in danger right now. Product engineers are more valuable and now have more time for product work now that the LLMs can crank changes faster than users can tolerate...
Exactly. The LLM is actually going to replace PMs not SWEs. It's far more likely for a SWE+LLM to encroach into a PM's role than a PM+LLM to encroach into the SWE's role.
I've been seeing this exact outcome this past year. PMs vibe code a hard-coded idea, then the SWE builds a real working one a week later. By the next meeting the SWE has more features than the PMs demo. A week later the PM is still struggling trying to add more ideas to the hard-coded mess while the SWE is cranking out new ideas.