The Corrupted Zip File
We almost gave up on this one.
Paperclip – the AI company we designed last week – needs agents that can do full edit-build-deploy cycles on the VPS. Edit Pascal source, compile to JavaScript, deploy to the sandbox. No human in the loop. But the TMS Web Core compiler on Linux was failing with this:
Forward function not resolved "VarRecs"
The error pointed to system.pas, the most fundamental unit in the compiler’s runtime. We filed a support ticket with TMS Software and started planning a workaround: agents edit the code on the VPS, then hand off to a Mac for compilation. It would work, but it would break the autonomy model. Every build would need a human-adjacent machine.
We were resigned to it. The board was not.
The wrong trails
First theory: permissions. Maybe the compiler couldn’t read something it needed. Checked. Everything was readable.
Second theory: missing JavaScript support files. TMS Web Core compiles Pascal to JS, and the runtime needs certain JS files present. They were all there.
Third theory: maybe the FNC UI Pack component library had the same problem. Tested it – compiled clean. So the compiler works, just not for system units? That didn’t make sense.
It made sense later. The FNC UI Pack “compiled” successfully because it shipped with pre-compiled .js output files dated February 3. The compiler never actually processed those units on Linux. It just found the existing output and moved on. A false positive that cost us a day of wrong assumptions.
The VarRecs question
VarRecs is a compiler intrinsic. On Mac and Windows, the pas2js compiler synthesizes its implementation as inline JavaScript – it’s one of those functions that doesn’t exist in source code because the compiler generates it on the fly. The declaration lives in system.pas, but the implementation is the compiler itself.
So why was the Linux compiler choking on it?
TMS support responded to the ticket: their system.pas has both the declaration AND the implementation. Ours only had the declaration. Our system.pas was a 16KB file with an interface section full of declarations and a completely empty implementation section.
Where was the real one?
Two zip files
The TMS Web Core VS Code extension ships with two zip files bundled inside it: TMS_WEB_CORE_pju.zip and TMS_Web_Core_Source.zip. The extension’s extension.js downloads these from a Backblaze B2 CDN on activation and extracts them. The source zip contains the full Pascal source files. The pju zip contains pre-compiled .pju unit files – the compiler’s equivalent of .dcu files in Delphi.
The .pju files are the key. When the compiler finds a .pju file for a unit, it uses that instead of recompiling from source. system.pju contains the compiled version of system.pas – including the synthesized VarRecs implementation that doesn’t exist in the source.
The pju zip should have been extracted automatically during extension activation. It wasn’t.
400KB vs 2.6MB
The TMS_WEB_CORE_pju.zip file on disk was 400KB.
The same file downloaded fresh from the CDN (https://f003.backblazeb2.com/file/TMSSOFTWARECDN/2198/TMS_WEB_CORE_pju.zip) was 2.6MB and contained 222 .pju files.
The download had been truncated. The extension’s activation code caught the extraction failure in a try/catch block, logged nothing useful, and continued. The .pju files were never installed. The compiler fell back to recompiling from the stripped-down source system.pas, hit the missing VarRecs implementation, and died.
A truncated HTTP download. Silent error handling. That was the entire blocker.
We extracted the full zip. system.pju appeared in the units directory. Compiled. No VarRecs error.
The second wall
Of course it wasn’t that clean.
The next compilation attempt hit a different error: Character.pas also had an empty implementation section, and unlike system.pas, there was no .pju file for it in the zip. TCharHelper – IsDigit, IsLetter, ToUpper, ToLower, and friends – all declared, none implemented.
These are JavaScript-native operations. IsDigit is a regex test. ToUpper is String.toUpperCase(). So we wrote the implementation ourselves, using pas2js asm blocks to bridge to the JavaScript:
class function TCharHelper.IsDigit(C: Char): Boolean;
begin
asm
Result = /^[0-9]$/.test(C);
end;
end;
class function TCharHelper.ToUpper(C: Char): Boolean;
begin
asm
Result = C.toUpperCase();
end;
end;
Twelve methods, each a one-liner in JavaScript. The kind of code that takes two minutes to write and two days to discover you need.
The last mile
Two more issues before the build was truly clean:
The compiled output included a “trial version” alert dialog – the TMS license had expired or wasn’t configured for the Linux environment. We ran the TMSLicenseGeneratorCmd tool to regenerate it, and the alert disappeared.
Then we updated build.sh to handle the deployment side: automatically patching hardcoded production API URLs to sandbox URLs, deploying the compiled output to versioned folders so each build is isolated and rollback is trivial.
What changed
Before: agents edit Pascal source on the VPS, then we figure out some handoff to get it compiled on the Mac, then we figure out some handoff to get it deployed back to the VPS. Every build touches two machines and needs human coordination.
After: agents edit, compile, and deploy on the VPS. One machine. No handoff. The full cycle runs in seconds.
The Paperclip agents can now autonomously modify the application and see the results. The sandbox is real.
The reflection
The root cause was a corrupted zip file. Not a compiler bug. Not a platform incompatibility. Not a missing feature in the Linux toolchain. A truncated HTTP download that silently failed, leaving the compiler without the pre-compiled units it needed to function.
We were ready to give up. We had a workaround planned. The board pushed us to keep digging – to look at the extension’s activation code, to check the zip files, to compare file sizes. That push turned “we need a Mac in the loop” into “the agents are fully autonomous.”
Sometimes the most important debugging skill isn’t technical. It’s the willingness to keep looking when you’ve already found an answer that’s good enough.