Blog › ICP guides
OpenOffice Basic developer on retainer: document resource leak in loop, LibreOffice Basic developer, StarBasic developer on monthly retainer
October 9, 2026 · ~15 min read
An OpenOffice Basic developer was maintaining a monthly billing consolidation macro for a regional accounting firm. The macro ran on the first Monday of each month to consolidate invoice data from 47 individual client .ods spreadsheets stored on a Samba share into a single billing_master.ods summary file. The macro used a loop: for each client spreadsheet file, call createUnoService("com.sun.star.frame.Desktop").loadComponentFromURL(sFilePath, "_blank", 0, aArgs) to open the file, read the invoice totals using UNO’s sheet and cell API, append a summary row to billing_master.ods, and then advance to the next file. The macro never called oDoc.close() after reading each client file. OpenOffice accumulated 47 open document instances in the UNO component environment. By the 38th file, the UNO document component registry was near its resource limit. loadComponentFromURL for files 38–47 returned a document component that was not fully loaded — the UNO sheet interface was unavailable or returned empty cells. The macro appended blank or zero-value summary rows for clients 38–47 to the billing master. The billing manager discovered 10 missing client invoices during the month-end review: clients 38–47 had $0 totals in the master file despite having complete data in their individual files. Silent data omission: 10 clients per month → 0 after adding oDoc.close(True) after each file is processed.
The root cause was UNO document resource leak in the OpenOffice/LibreOffice component environment. loadComponentFromURL in OpenOffice/LibreOffice Basic opens a document as a UNO component and registers it with the LibreOffice component environment — the com.sun.star.frame.Desktop service manages all open documents. Each loadComponentFromURL call increments the count of open document components tracked by the UNO runtime. Without a matching oDoc.close(True) call, the document component remains registered as “open” in the UNO environment even after the macro variable oDoc goes out of scope or is reassigned to the next file. LibreOffice’s UNO component environment has a practical limit on simultaneously-registered document instances, related to the com.sun.star.container.XIndexAccess document enumeration maintained by StarDesktop.getComponents(). Near this limit, loadComponentFromURL may return a partially-initialized document component — the getSheets() interface is available but getByIndex(0) returns a sheet with no cells loaded. The macro’s cell-value reads return empty strings or zero. No exception is thrown. The appendSummaryRow sub-procedure silently writes the empty values. LibreOffice itself remains running and responsive throughout.
The invisibility of this bug operates on two layers. First, the macro developer who tested the consolidation procedure ran it against a 5-file test set, then a 20-file set — both well under the resource limit. All test runs produced correct results. The failure only manifests after file count 37–40; the exact threshold varies by LibreOffice version, system memory, and whether other LibreOffice documents are open during the run. Second, the symptoms — blank rows in the master file — look identical to the case where a client’s ODS file is genuinely empty or has a different sheet structure than expected. The billing manager’s first hypothesis was that the template macro was using the wrong cell reference for those specific clients. Only after comparing the individual client .ods files (which had complete data) to the master (which showed zeros) for all 10 clients in the affected range, and noticing that the missing clients were always the same sequential block at the end of the alphabetical file list, did the file-count correlation become apparent. That sequential pattern — always the last 10 in the alphabetical sort, never a random subset — was the diagnostic signature of a resource limit being crossed at a fixed file count rather than a data-structure problem in specific files.
StarOffice, OpenOffice.org, and LibreOffice: 25 years of the open-source office suite
The lineage of OpenOffice Basic begins in Hamburg, Germany, where StarDivision GmbH was founded by Marco Börries in 1985. StarDivision developed StarOffice, an integrated office suite for MS-DOS, Windows, OS/2, and Linux. In 1998, StarDivision made StarOffice for Linux available as a free download, gaining significant adoption as a Microsoft Office alternative at a moment when Linux desktop use was growing but affordable productivity software was scarce. The Linux developer community, small businesses in Germany, and academic institutions in Europe adopted StarOffice in large numbers during 1998 and 1999. Sun Microsystems acquired StarDivision in August 1999 for $73.5 million, recognizing StarOffice as a strategic asset in Sun’s long-running competition with Microsoft on the enterprise desktop. Sun released OpenOffice.org 1.0 in April 2002, open-sourcing the StarOffice codebase under the GNU Lesser General Public License (LGPL) and the Sun Industry Standards Source License (SISSL). With this release, the macro language StarBasic became OpenOffice Basic, and the internal component architecture — UNO (Universal Network Objects) — was exposed as a public API that allowed Basic macros, Python scripts, and external programs to control every aspect of an OpenOffice.org document through a unified component object model similar in concept to Microsoft’s COM/DCOM. The UNO API architecture became the technical foundation for the organizational macro automation that retainer developers maintain today.
Oracle Corporation acquired Sun Microsystems in January 2010 for $7.4 billion. The OpenOffice.org community, concerned about Oracle’s commitment to continued open development of the suite, forked the codebase in September 2010 to create LibreOffice under The Document Foundation, a newly formed independent foundation. LibreOffice 3.3 was released in January 2011, just months after the fork, with initial changes focused on removing encumbrances, improving build infrastructure, and addressing long-standing issues from the OpenOffice.org era. Oracle subsequently donated the OpenOffice.org trademark and codebase to the Apache Software Foundation; Apache OpenOffice (AOO) 3.4 was released in May 2012. The two projects — LibreOffice under The Document Foundation and Apache OpenOffice under the Apache Software Foundation — now maintain separate codebases from the same original StarOffice lineage. LibreOffice remains actively developed with monthly releases; Apache OpenOffice is substantially less active in terms of release cadence. Most enterprise Linux distributions — Red Hat Enterprise Linux, Ubuntu LTS, SUSE Linux Enterprise — ship LibreOffice as the default office suite. A significant number of organizations still run OpenOffice.org 3.x or 4.x (the last Apache OpenOffice release being 4.1.x) on older Windows XP and Vista desktops in locked-down environments where upgrading the OS is not an option, creating the retainer market for developers who can work with both the older OpenOffice.org UNO API surface and the current LibreOffice 7.x API.
OpenOffice Basic, LibreOffice Basic, and StarBasic (the three names refer to the same macro language across its different eras) have been the dominant platform for business automation in organizations that use the open-source office suite. The macro recorder and Basic IDE are built into the office suite, making them accessible to power users who are not professional developers. The UNO API provides programmatic access to every document, sheet, cell, paragraph, drawing shape, presentation slide, and document property. Common automation patterns include invoice consolidation from multiple ODS files (the pattern at the center of this post’s production bug), automated report generation from LibreOffice Calc data, mail-merge automation in LibreOffice Writer using the com.sun.star.text.MailMerge service, and batch PDF export from Impress presentations using com.sun.star.beans.PropertyValue arrays with the PDF export filter. Organizations that built these macros in the OpenOffice.org 2.x or 3.x era under a staff developer who has since left the organization, or whose macro requirements have grown beyond what a non-developer staff member can maintain, are the core retainer client for an OpenOffice Basic or LibreOffice Basic specialist. The retainer developer maintains the macros as the organization’s office suite version changes, as the file count processed by batch macros grows past the thresholds tested during original development, and as the original developer’s undocumented assumptions — like “this macro will only ever process 20 files” — are violated by operational reality.
LibreOffice UNO API, loadComponentFromURL, and document resource management
LibreOffice’s internal architecture is built on UNO (Universal Network Objects), a component object model similar in structure to Microsoft’s COM/DCOM. Every LibreOffice document is a UNO component registered with the com.sun.star.frame.Desktop service, which acts as the central document manager for the LibreOffice process. StarDesktop.getComponents() returns an XIndexAccess container enumerating all currently open document components. When a macro calls oDesktop.loadComponentFromURL(sURL, "_blank", 0, aArgs), LibreOffice creates a new UNO component, loads the document from the specified URL, registers the component in the StarDesktop component list, and returns a reference to the component. The macro variable oDoc holds this UNO interface reference. oDoc.close(True) signals the document component to close: it unregisters the component from the StarDesktop component list, releases the memory allocated to the document model, and — if the Boolean argument is True — saves the document first. oDoc.close(False) closes without saving, which is correct for read-only source files in a consolidation loop. Without a oDoc.close() call, the component registration persists in the UNO runtime’s component registry until the LibreOffice process exits. The UNO runtime does not garbage-collect document components when the Basic variable referencing them is reassigned or goes out of scope. The component remains open, consuming memory and a slot in the StarDesktop component enumeration, regardless of whether any Basic variable still holds a reference to it.
The diagnostic tool that reveals the leak is StarDesktop.getComponents().getCount(), which returns the number of document components currently registered with the LibreOffice desktop. In a correctly written consolidation loop that calls oDoc.close(False) after each source file, this count stays at 1 throughout the entire run (only billing_master.ods, the output file, remains open). In a leaking loop over 47 source files, the count climbs to 48 by the end of the loop (47 source files plus billing_master.ods, none of the source files ever closed). The practical symptom threshold at which loadComponentFromURL begins failing varies significantly across platforms. On LibreOffice 7.x with 8 GB of RAM and no other LibreOffice documents open, the limit is typically 80–120 concurrent components before load failures become systematic. On OpenOffice.org 3.4 with 2 GB of RAM — typical of the XP-era Windows hardware on which many of these macros were originally deployed and remain in production — the threshold is 30–50 concurrent instances, which is why a macro originally tested on a 20-file set fails in production with a 47-file set. The diagnostic procedure is direct: add a MsgBox StarDesktop.getComponents().getCount() call inside the loop at iteration 45. If the message box shows 45 (or 46 including the master), the macro is not closing documents. Add oDoc.close(False) and oDoc = Nothing after the summary row append, re-run, and verify that the count stays at 1 at every iteration.
The corrected loop has a specific form that addresses both the UNO component registry leak and the Basic runtime interface reference: oDoc = oDesktop.loadComponentFromURL(sURL, "_blank", 0, aArgs) opens the source file; the macro reads invoice data from the sheet and cell API (oDoc.getSheets().getByIndex(0).getCellByPosition(col, row).getValue()); the appendSummaryRow call writes to billing_master.ods; oDoc.close(False) closes the source file without saving and unregisters the UNO component from the StarDesktop component list; oDoc = Nothing releases the UNO interface reference held by the Basic variable, preventing potential use-after-free behavior on older LibreOffice and OpenOffice versions where the interface reference might be accessed after the component has been unregistered. The distinction between oDoc.close(True) and oDoc.close(False) matters in principle but not in practice for unmodified read-only source files: LibreOffice only writes to disk on close(True) if the document’s internal “modified” flag is set; a source file that was only read from will have its modified flag clear, so close(True) and close(False) are operationally equivalent for it. The safer convention for a consolidation macro that should never modify source files is to use oDoc.close(False) explicitly, signaling intent to any future maintainer: these are read-only source files, do not save them.
Typical OpenOffice Basic developer retainer work and what it looks like in a work log
UNO document resource leak in consolidation loop is the canonical OpenOffice Basic invisible production bug, and the work log entry must be specific to be auditable. The pattern is consistent: a macro sub-procedure (typically named something like ConsolidateInvoices, MergeMonthlyData, or BuildSummaryReport) uses a For loop over a directory listing of .ods files; inside the loop, createUnoService("com.sun.star.frame.Desktop").loadComponentFromURL(sFilePath, "_blank", 0, aArgs) opens each file; the UNO sheet and cell API reads the required data; a appendSummaryRow or equivalent call writes to the master file; and then the loop variable increments to the next file without calling oDoc.close(). An auditable work log entry for this investigation reads: “billing_master.ods macro module BillingConsolidation; sub-procedure ConsolidateClientInvoices; loadComponentFromURL call at line 47 of the loop body; 47 client .ods files processed; oDoc.close() absent; StarDesktop.getComponents().getCount() returning 38 at iteration 38; loadComponentFromURL returning partially-initialized component at iterations 38–47; getSheets().getByIndex(0).getCellByPosition(2,1).getValue() returning 0 for iterations 38–47; 10 clients (files 38–47 in alphabetical order) showing $0 in billing_master.ods despite complete data in individual .ods files; fix: oDoc.close(False) added after appendSummaryRow call; oDoc = Nothing added after close; getCount() stays at 1 throughout loop after fix; 10 missing client rows: 0 after fix; 4h.” A log entry that says “fixed billing macro, missing data, 4h” is not auditable. The specific file count, the specific iteration at which failure begins, and the specific UNO component count at that iteration are the diagnostic evidence that makes the log defensible to the accounting firm’s management and auditors.
OpenOffice Basic macro for report generation and batch PDF export is the second most common retainer engagement type. The typical pattern involves a macro that reads data from a LibreOffice Calc spreadsheet, populates a Writer template with the data using the com.sun.star.text.XText and com.sun.star.text.XTextCursor interfaces, and exports the populated document as a PDF using the storeToURL method with a com.sun.star.beans.PropertyValue array specifying FilterName as "writer_pdf_Export" or "calc_pdf_Export" depending on the document type. A common issue in this pattern is macro security trust settings: LibreOffice’s default macro security level is “High”, which blocks execution of macros not signed with a trusted certificate. Organizations that deploy LibreOffice macros on shared Samba drives often encounter the situation where the macro runs correctly on the developer’s machine (where the macro location is in the trusted paths list) but prompts a security warning or silently fails on other users’ machines. The retainer developer’s work includes configuring the LibreOffice macro security trust settings via Tools > Macros > Organize Basic Macros > Security, or via the com.sun.star.configuration.ConfigurationProvider UNO service for automated deployment, and documenting the correct trust configuration in the organizational LibreOffice deployment guide. Work log: “ReportGenerator module in reports_template.ods; batch PDF export loop; com.sun.star.beans.PropertyValue array for FilterName=writer_pdf_Export; macro security trust error on shared drive deployment; trusted path configuration via Tools > Macros > Security on 3 user machines; PDF export loop tested against 15 Calc data rows; all 15 PDFs exported correctly; 2.5h.”
LibreOffice macro upgrade work — migrating OpenOffice.org 3.x macros to LibreOffice 7.x — constitutes the third major retainer engagement category and often the most time-consuming. OpenOffice.org 3.x and 4.x macros frequently use UNO service names or interface methods that have been deprecated or renamed in LibreOffice 7.x. Common migration issues include: the com.sun.star.lang.XMultiServiceFactory service creation pattern being superseded by createUnoService in some contexts; cell range address structures changing their field names between OOo and LO versions; the com.sun.star.sheet.SpreadsheetDocument interface adding new methods whose optional-parameter behavior differs from OOo 3.x behavior; and the Basic IDE itself having a slightly different debugger behavior in LibreOffice 7.x compared to OpenOffice.org 3.x, which can cause macros that relied on implicit type coercion in OOo to produce type-mismatch errors in LO. The retainer developer’s migration work includes: inventorying all createUnoService and createUnoStruct calls to verify the service names are valid in the target LibreOffice version; testing cell-access patterns against the new com.sun.star.sheet.SpreadsheetDocument interface; and running the full file-count production load (47 files, not the 5-file development test set) against LibreOffice 7.x to confirm no new resource limits are encountered. Work log: “BillingConsolidation module migration from OpenOffice.org 3.4 to LibreOffice 7.6; 3 deprecated service references updated; cell range address field name change (StartColumn/StartRow to StartColumn/StartRow confirmed unchanged, EndColumn/EndRow confirmed unchanged); 47-file production load test on LibreOffice 7.6.7 with oDoc.close(False) in loop; getCount() stays at 1; all 47 summary rows populated correctly; 6h.”
Track OpenOffice Basic developer retainer hours without the status emails
When a 4-hour investigation traces 10 missing client invoice rows per month to a UNO document resource leak in a consolidation loop — loadComponentFromURL accumulating 38+ open document instances until the UNO component registry returns partially-initialized documents, getSheets().getByIndex(0).getCellByPosition() returning zero for clients 38–47, silence throughout, no exception raised — the work log must name the macro module, the missing client file range (files 38–47 in alphabetical order), the StarDesktop.getComponents().getCount() at the failure threshold, and the fix. HourTab gives your OpenOffice Basic retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the specific file-count threshold, the missing clients, and the oDoc.close(False) fix. No client login. No status emails. CSV in, URL out.
How HourTab tracks OpenOffice Basic developer retainer hours
The UNO document resource leak is invisible in development testing by a mechanism that is structurally identical across every resource-exhaustion bug in batch-processing macros: the developer who tests the consolidation procedure against a 5-file test set, then a 20-file test set, never observes a failure because both test sets are well under the resource limit. The 5-file test run produces 5 correct summary rows. The 20-file test run produces 20 correct summary rows. Every test assertion passes. The macro ships to production and runs correctly every month until the file count is large enough — or system memory under competing load is constrained enough, or other LibreOffice documents happen to be open during the consolidation run — that the accumulated open document instance count crosses the threshold at which loadComponentFromURL begins returning partially-initialized components. On the accounting firm’s XP-era Windows machine running OpenOffice.org 3.4 with 2 GB of RAM, that threshold is approximately 37–40 instances. On a newer LibreOffice 7.x installation with 8 GB of RAM, the same macro might run correctly for 80 files before failing. The failure threshold is invisible to the developer because it depends on runtime environment variables that are never replicated in development testing at normal file counts. The only way to verify the fix is to test at production file count (47 files) with StarDesktop.getComponents().getCount() instrumentation active throughout the loop.
The auditable work log entry for this bug must capture: macro module name and sub-procedure name; the loadComponentFromURL call site; the total file count (47); the first iteration at which failure begins (38); the StarDesktop.getComponents().getCount() value at that iteration (38 open instances); the failure mode (partially-initialized document component, getSheets().getByIndex(0) returns sheet with no cells loaded, getCellByPosition(col, row).getValue() returns 0); the affected clients (files 38–47 in alphabetical sort order, 10 clients); the symptom in the master file (10 rows showing $0 totals); the fix (oDoc.close(False) after each source file, oDoc = Nothing after close); the verified post-fix behavior (getCount() stays at 1 throughout the 47-file run, all 47 rows correct in billing_master.ods); and the total hours. A log entry that says “investigated missing billing data, found bug in macro, fixed it, 4h” is not auditable by the accounting firm’s management. The specific iteration count, the specific UNO component count at the threshold, and the specific client range affected are the evidence that justifies the investigation hours and documents the production risk that was resolved.
Related retainer patterns in the same resource-not-released class: Authorware developer retainers cover the ADODB.Connection leak in a loop — the same structural pattern of a database connection opened per iteration without being closed, accumulating until resource exhaustion causes subsequent opens to fail silently; the auditable work log structure is identical (iteration count, resource count at failure, affected records, fix). Lingo developer retainers cover the Lingo FileIO Xtra not released — a Director Lingo macro that creates a FileIO Xtra instance per file access without calling xtra.destroy(), accumulating Xtra instances until the Director runtime runs out of Xtra slot capacity; same resource-not-released class, same invisible-in-development-testing characteristic. Lotus Notes developer retainers cover the NotesDocument.Save() return value silent save failure — a different mechanism (the save succeeds at the API call level but the return value indicates a conflict or validation failure that the macro never checks) but the same invisible-data-loss audit pattern: records that appear to be saved are not present in the Notes database, the macro reports no error, and the data loss surfaces only during a downstream audit.
FAQ: OpenOffice Basic developer retainers
What does an OpenOffice Basic developer on retainer typically do?
An OpenOffice Basic developer on monthly retainer covers UNO document resource leak audits (reviewing every macro that calls loadComponentFromURL in a loop to confirm oDoc.close() is called with the correct Boolean argument after each file is processed); silent data-loss diagnosis in batch consolidation macros (correlating master spreadsheet row counts against the source file count, identifying the file-count threshold at which loadComponentFromURL begins returning partially-initialized document components, and tracing the missing summary rows to accumulated open document instances); OpenOffice Basic and LibreOffice Basic macro upgrades (migrating OpenOffice.org 3.x macros to LibreOffice 7.x, updating deprecated UNO service references, fixing cell-access API changes); StarBasic macro audits for report generation, mail-merge automation in Writer, and batch PDF export from Impress presentations; com.sun.star.beans.PropertyValue array configuration for PDF and spreadsheet export filters; and macro security trust configuration for organizational deployment of LibreOffice Basic macros on shared drives.
What OpenOffice Basic macro bug work is most commonly underlogged?
UNO document resource leaks in consolidation loops — where a LibreOffice Basic batch macro opens each source .ods file with loadComponentFromURL but never calls oDoc.close(), accumulating open document instances until the UNO component registry returns partially-initialized documents; getSheets().getByIndex(0).getCellByPosition() returning empty strings or zero for the affected files; the macro appending blank or zero-value summary rows; no exception thrown; LibreOffice remaining responsive throughout — are the most systematically underlogged OpenOffice Basic retainer work. The developer who tested against a 5-file or 20-file set never observed the failure because both were under the resource limit. The silent data omission surfaces during month-end review when the billing manager notices that 10 clients show $0 in the master despite complete data in their individual files, with the missing clients always forming the same sequential block at the end of the alphabetical file list. The work log must name the macro module, the loadComponentFromURL call site, the iteration at which failure begins, the StarDesktop.getComponents().getCount() at that iteration, the affected file range, the missing rows, and the fix.
What are typical OpenOffice Basic developer retainer rates?
Entry-level OpenOffice Basic developers with experience in LibreOffice Basic macro recording and editing, basic UNO cell and sheet access (oSheet.getCellByPosition, oCell.getString, oCell.getValue), and simple document open and save operations typically bill at $45 to $75 per hour. Mid-level LibreOffice Basic developers with experience in batch document processing loops using loadComponentFromURL, UNO service manager initialization (createUnoService), com.sun.star.beans.PropertyValue arrays for export filters, document resource lifecycle management (oDoc.close with correct Boolean argument), and StarBasic macro debugging in the LibreOffice Basic IDE typically bill at $70 to $120 per hour. Senior OpenOffice Basic and StarBasic developers with deep knowledge of the full UNO API component model, StarDesktop.getComponents() enumeration, loadComponentFromURL failure modes, LibreOffice 7.x API migration from OpenOffice.org 3.x and 4.x, and production forensics on large-file-count batch consolidation macros typically bill at $100 to $180 per hour. Monthly retainer ranges: $1,200 to $2,000 per month for advisory engagements covering macro audits and UNO resource leak reviews; $1,800 to $3,500 per month for active macro development and LibreOffice migration work.
What should an OpenOffice Basic developer retainer agreement include?
An OpenOffice Basic developer retainer agreement should specify: the target office suite version (LibreOffice 7.x, Apache OpenOffice 4.1.x, or an older OpenOffice.org 3.x installation — each has different UNO API surface area and different loadComponentFromURL resource limit thresholds under load); whether the retainer covers LibreOffice Basic macro development, OpenOffice.org Basic macro maintenance, or both; whether the retainer includes UNO document resource leak audits (reviewing every loadComponentFromURL call in a loop to confirm oDoc.close() is present with the correct Boolean argument); the file-count scope of batch consolidation macros (a macro that processes 10 files has a different risk profile than one that processes 50 or 150); whether the retainer covers macro security trust configuration for shared Samba drive deployment; whether the retainer includes migration scoping from OpenOffice.org 3.x or 4.x macros to LibreOffice 7.x; and whether the retainer covers batch PDF export macros, Writer mail-merge automation, or other UNO API integration patterns beyond spreadsheet consolidation.
How should OpenOffice Basic developer retainer hours be logged?
Log each OpenOffice Basic retainer session with the macro module name, the loadComponentFromURL call site, the file count processed, and the before-and-after outcome. For UNO document resource leak in consolidation loop: macro module (BillingConsolidation in billing_master.ods), sub-procedure (ConsolidateClientInvoices), loadComponentFromURL call (line 47 of loop body), file count (47 client .ods files), open-without-close pattern (oDoc.close() absent), StarDesktop.getComponents().getCount() at iteration 38 (38 registered instances), failure mode (getSheets().getByIndex(0).getCellByPosition(2,1).getValue() returning 0 for iterations 38–47), missing rows in billing_master.ods (10 rows, clients 38–47 in alphabetical order, all showing $0), fix (oDoc.close(False) after appendSummaryRow, oDoc = Nothing after close), verified post-fix behavior (getCount() stays at 1, all 47 rows correct), hours (4h). For LibreOffice 7.x migration: macro module, deprecated API calls, replacement services, 47-file production load test result, hours. For batch PDF export macro: module, PropertyValue array for PDF filter, file count, export path validation, hours.