How to Use IronWord with ChatGPT Codex

Curtis Chau
Curtis Chau
Updated: October 19, 2026

When a company changes its name, the legal team receives a folder of signed contracts to update. The old name is found in body paragraphs, party tables, upper-case titles, and page headers. Word can also store the name across several formatting runs, for example when one fragment was bolded years ago. A standard find-and-replace on each file fixes only some cases, while others are left unchanged with no alert.

ChatGPT Codex is the coding agent from OpenAI. Inside a repository it edits files, executes commands such as dotnet run, then summarizes the changes. One agent serves the terminal, IDEs, the ChatGPT desktop app and cloud tasks. IronWord, a .NET library from Iron Software, opens existing Word documents as an object model of paragraphs, runs and tables that code can edit and save without Microsoft Office.

Codex works on a contract backlog with a ground-truth manifest. The batch prompts write a CSV with one row per contract, so each count Codex reports from them is backed by a file.

Quickstart
NuGetInstall with NuGet

PM > Install-Package IronWord

Install IronWord by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

Start Codex in a .NET console project that contains the contract folder, and request an inventory before any edit.

$ironword Add the IronWord 2026.10.4 NuGet package to this console project.
Then have Program.cs open every .docx in Contracts/sample/ without changing it.

For each file, write one row to out/quickstart.csv with these columns:

- file
- body count, how often "Northwind Traders" appears in the body text
- table count, the same name counted separately in every table cell
- the exception type, when the file fails to open

Print the totals by reading that CSV back.
Program.cs reads the key from IRONWORD_LICENSE_KEY itself.
Don't print the key's value, and don't list environment variables.
Nothing gets saved here, so an unset key is no reason to stop.
Build and run it with at most three repair attempts, then tell me the result.
Text


What is ChatGPT Codex, and how does it fit into .NET Word document work?

Installation steps are in the Codex CLI documentation, and releases are published in the open-source repository.

Codex knows IronWord from training data and supplied files, and a new release can make both out of date. The 2026.10 releases load and save through streams and asynchronous calls.

How do you write an IronWord prompt and set up Codex for it?

An effective batch prompt names the input, each location of the name, the output, the proof, the read-only ground truth and the constraints.

OpenAI's paper on why language models hallucinate traces confident wrong answers to training and evaluation that reward guessing, and in a rename job the guess is that every contract is clean.

Codex looks for skills in .agents/skills in every directory between the working directory and the repository root. Saved as .agents/skills/ironword/SKILL.md, IronWord's skill.md resolves the $ironword mention.

Before any work, Codex reads at most one AGENTS.md in each directory on the path between the repository root and the working folder. The file nearest the working folder takes precedence. The /init command drafts a starting AGENTS.md.

Give this repository a top-level AGENTS.md with a Contract rebrand section.
List these rules in it:

- No DOCX library other than IronWord is allowed.
- IronWord's package is IronWord, and its namespaces are IronWord and IronWord.Models.
- The ironword skill in .agents/skills/ironword/ is one API reference.
- https://ironsoftware.com/word/csharp/llms.txt is the other.
- Contracts/inbox/ and Contracts/manifest.csv are read-only.
- Rebranded copies go to out/rebranded/ under their original names.
- IRONWORD_LICENSE_KEY holds the key, and its value never appears in output, logs or files.
- Checking the variable means its presence and length, nothing more.
- Any count you report is read from a CSV written in the same turn.
- The reply names that CSV.
Text

How do you ask Codex to add IronWord and check the license?

Print the IronWord PackageReference from ContractRebrand.csproj and the version restore resolved.
Then quote every IronSoftware entry that dotnet list package --include-transitive prints.
Leave the package references exactly as they are.
Text

IronWord 2026.10.4 references IronSoftware.Abstractions 2026.1.2, IronSoftware.Common 2026.4.2 and IronSoftware.System.Drawing 2026.11.3. Restore also listed IronSoftware.Drawing.Abstractions 2026.1.2. The build copies native LayoutEngineInterop libraries for Windows, Linux and macOS into a runtimes folder. The OpenXML conflict page covers a conflict with a newer DocumentFormat.OpenXml in releases before 2025.7.13. Check the IronWord changelog before pinning.

Without a key, opening, reading and replacing succeed, but every save overload, asynchronous ones included, throws a LicensingException with the message Production License Required.

Add Startup.ApplyIronWordKey and call it first in Program.cs.
It copies the IRONWORD_LICENSE_KEY value into IronWord.License.LicenseKey.
It also reports whether License.IsLicensed is true.

Add a --dry-run switch that opens and edits in memory and never saves.
Then run the sample twice with the variable removed:

1. Run it once with --dry-run.
2. Run it once copying each file to out/licence-check/ through SaveAs.

Clear the variable inside each child process only, never for the session.
Write out/licence-check.csv with the columns run, file, call, exception type name and message.
Tell me which calls threw.
The licensing exception type isn't public, so catch Exception and record its GetType().FullName.
Stop after three repair attempts.
Text

The license keys page also covers Web.config, App.config and appsettings.json.

Which parts of a Word document does a text replacement reach?

Create Services/MentionScanner.cs.
For one DOCX it returns a count for each of these:

- "Northwind Traders" in body paragraphs
- "Northwind Traders" in table cells
- the name in upper case
- the name inside "Northwind Traders Pension Trust"
- the name in headers and footers
- the old notice address
- the Fax notices paragraph

The body and table cell counts leave out the pension trust name.
IronWord has no header or footer model.
For that column only, read word/header*.xml and word/footer*.xml with System.IO.Compression.

Then run it over Contracts/inbox/ into out/inventory.csv.
Mark files that fail to open as Unreadable.
List every file whose counts differ from manifest.csv.
In that comparison, the body count is body_mentions plus split_run_mentions.
Text

The built-in explorer agent tests the replacement on real files.

Create a scratch console project under scratch/replace-probe/ yourself.
Add a Compile Remove line for scratch/** to ContractRebrand.csproj.

Then ask the explorer agent to use that project.
It finds which parts of a contract the document-level ReplaceText call changes.
It tests copies of the Contracts/sample/ files in memory, with these four cases:

- a name split across runs
- a table cell
- the upper-case title
- the pension trust

The header has no object in the API, so the explorer answers that one from IronWord.xml.
It changes nothing under Services/ or in Program.cs.

Write the five answers to out/replace-reach.csv.
Beside each answer, quote the ironword skill or IronWord documentation.
Where a page and the test disagree, say so.
Text

Calibration on 2026.10.4 gave these in-memory results.

LocationDocument-level replaceWhat reaches it
Body, one runReplacedThe document call
Body, split across runsReplacedThe document call
Table cellUnchangedReplaceText on each TableCell
Upper-case titleUnchanged (case-sensitive match)A second call with the upper-case form
Pension trust nameRenamed (plain substring match)A placeholder substituted first and restored afterward
Header or footerNot in the object modelReport it, then edit manually

The split-run row has a side effect. A name stored as a bold North followed by a plain wind Traders becomes one bold run that reads Contoso Ltd.

A check built on the document's ExtractText, which returns body paragraphs only, passes a contract whose party table still names the old company. FindText misses a split-run match, since it matches only a text element that holds the whole phrase.

How do you replace text across a folder of documents?

Have the worker agent write Services/Rebrander.cs.
It applies every rule in Contracts/README.md to Contracts/inbox/.
It builds on the explorer's findings and follows these rules:

- Replace inside each table cell as well as the body.
- Keep the pension trust intact.
- Delete the Fax notices paragraph.
- Leave header and footer parts as they are, since IronWord can't edit them.
- Report those contracts as HeaderFlagged.
- Save each copy to out/rebranded/ with SaveAs.
- Never call Save() on a document opened from inbox/.

Add a --sequential mode that rescans the saved copies with MentionScanner.
It writes out/sequential.csv with these columns:

- file
- status from the README, always one of its four
- mentions left per location
- milliseconds
- header_lost, set to yes when a copy lacks a header or footer part its original had

The header_lost column is reported on its own and never changes a status.
Tell me the status counts.
Text

In IronWord 2026.10.4, saving a loaded DOCX with a table discards every text edit inside the table and in any paragraph after the first table. The copy on disk keeps the old text, no exception is raised, and the in-memory document still shows the replacement. Edits before the first table persist, as does a cell replaced with a new TableCell. These contracts place the signature after the party table, so an in-memory check passes copies whose saved signature never changed.

The parameterless Save is documented to overwrite a loaded document's source file, so a batch should not call it. The remove text guide shows the paragraph removal behind the fax rule.

In calibration, loading a zero-byte file threw NullReferenceException, and a text file, a truncated package and a package of random bytes each threw a FileFormatException from an obfuscated namespace with the message File contains corrupted data. A catch for the System.IO type misses it. Catch Exception per file and record its type.

Copies use SaveAs, which IronWord's troubleshooting page names for a header that disappears after saving.

Check whether SaveAs lost a header anywhere in this backlog.
IronWord's troubleshooting page header-disappears-after-saveas reports that in one environment.

For every HeaderFlagged file, compare the inbox original with its out/rebranded/ copy.
Look at the word/header*.xml and word/footer*.xml parts.
Write out/header-check.csv with these columns:

- file
- header parts before
- header parts after
- whether each part is present with its text unchanged

If a part went missing, rebrand that contract again from its inbox original.
Write it with the Save overload that takes the out/rebranded/ path.
Don't use the parameterless Save().
If the part is still missing, record it and stop.
Tell me which files lost a part.
Text

How do you process a folder of documents in parallel?

The asynchronous calls do file I/O without blocking, but the documentation states that parsing and serialization stay synchronous and that one document instance is not thread-safe.

Add a --parallel mode that works like this:

- It runs Task.WhenAll over the inbox.
- Each task holds a slot of a SemaphoreSlim whose size comes from --concurrency.
- Then it awaits WordDocument.LoadAsync and SaveAsAsync on its own WordDocument.

Run the mode three times:

1. At concurrency 1 into out/parallel-c1/.
2. At concurrency 4 into out/parallel-c4/.
3. At Environment.ProcessorCount into out/parallel-max/.

Write out/parallel-compare.csv with the columns file, folder and a yes or no.
The yes or no says whether the extracted body and cell text match out/rebranded/.
Log each run as a new line of out/runs.csv.
Give it the mode, concurrency, elapsed seconds and match count from out/parallel-compare.csv.
Tell me the quickest setting in runs.csv.
Text

In calibration, opening and editing the inbox in memory, four unreadable files included, took 4.8 seconds sequentially, 1.4 at a limit of 4 and 1.8 at 16, and with SaveAsAsync added 14.4, 5.5 and 4.7 seconds. The save is the limiting step.

How do you diagnose a document that still shows old text?

Codex accepts images through the -i flag (--image) on a new session or with codex exec resume, or as a paste into the composer.

I attached the first page of out/rebranded/MSA-1050.docx as an image with -i.
Its header still names Northwind Traders.
Look at the image, then explain from the file itself why the batch left that text.
Tell me whether out/sequential.csv already flags this file.
Don't change any code.
Text

A correct answer names the header part and the file's HeaderFlagged row.

How do you debug incorrect batch output end to end?

Find out why the nightly job passed contracts that legal later flagged.
The log reports 0 contracts still naming Northwind Traders.
Contracts/legal-review.csv lists contracts where legal still found the old name.
It was in the party table or an upper-case title.
The file also lists a pension trust that was renamed.

Send the explorer agent through Jobs/NightlyRebrand.cs and Logs/nightly-2026-10-07/ first.
Then write your own report in out/nightly-diagnosis.md with these parts:

- why the job's own check passed them
- the smallest fix
- the command that proves the fix

Don't run or edit any code.
Text

The diagnosis should cite the line where the job marks a contract clean.

How do you turn one-off code into reusable, tested components?

Put the worker agent on a RebrandJob class, built like this:

- Install the Microsoft.Extensions.DependencyInjection package.
- Have its container resolve RebrandJob.
- Set it up through a RebrandOptions record with inbox, output folder, concurrency and dry run.
- Keep every README rule, the header check and the per-file CSV.
- RebrandJob.cs stays under 180 lines, with XML doc comments.

The README rules cover body and cell replacement, upper case and the pension trust guard.
They also cover the address and the fax deletion.
Run the job over the whole inbox into out/final/ and out/final.csv.
Then tell me whether every status matches manifest.csv.
Also confirm no copy keeps an old name, old address or fax paragraph.
Old names in a copy's header or in the pension trust don't count.
Text

A refactor can remove a rule and still build.

Add an xUnit project at tests/ContractRebrand.Tests.
Exclude it from the main project's compile items, and join both projects in a solution file.

Write tests on real inbox files that check these:

- a split-run contract comes back with no old name
- a table contract is clean in its cells
- a pension trust mention survives
- a header contract is HeaderFlagged
- the zero-byte file is Unreadable without stopping the batch
- 20 files rebranded in parallel equal their sequential copies

Assert on extracted text and statuses, never on DOCX bytes.
Keep test copies in out/tests/work/.
Run dotnet test on the solution with a trx logger into out/tests/.
Report the totals from the trx file.
Text

OpenAI's custom prompts documentation deprecates prompts saved in ~/.codex/prompts in favor of skills, where recurring questions belong.

$ironword Look in the installed IronWord.xml.
List the WordDocument members that load or save a document, and answer these:

- Which ones are asynchronous?
- Which one overwrites the source file a document was loaded from?

List only members you can call from a line that compiles in a probe under scratch/.
Also name any member anywhere in IronWord.xml that is documented but doesn't compile.
Check by compiling only.
Don't run Save() on any document loaded from Contracts/inbox/.
Then show the line in RebrandJob that keeps Contracts/inbox/ untouched.
Text

How do you reuse these prompts on another document backlog?

With names, paths and rules changed, these prompts fit the next backlog, and the IronWord AI coding assistants guide covers setup for all five assistants.

What did the prompts produce in a real run?

The prompts ran in order in one Codex session. The .NET 9 project held 124 contracts and a manifest of expected statuses and counts, and every Release build succeeded. Our scanner rescanned every saved copy, and the tests were rerun in a fresh clone.

  • Before the rename, the inventory matched the manifest on all 124 files and marked the 4 non-DOCX files as Unreadable.
  • After the sequential rebrand, the 120 saved copies held none of the 114 old names above the party table, the 22 split across runs included.
  • None of the 13 upper-case titles, 38 old notice addresses or 57 fax paragraphs remained.
  • All 9 pension trust names stayed intact.
  • The old name remained in 28 party tables and 114 signatures, because the save discarded those edits.
  • The worker's sequential.csv gave each file the manifest's status, Rebranded 104, HeaderFlagged 10, NoChange 6 and Unreadable 4, but its own count columns still showed the 114 signature and 28 table mentions. The statuses record detection, not replacement, as Codex itself reported.
  • In the 10 sample files, the quickstart reported 19 body and 2 table mentions, against 16 and 2 in the manifest. Its body count also took the 2 cell mentions and the pension trust name.
  • Without the key, the dry run edited all 9 readable sample files in memory. The save run threw LicensingException at SaveAs for each of them.
  • At limits of 1, 4 and 32, the parallel runs took 11.456, 7.825 and 5.830 seconds. All 120 readable copies in each folder matched the sequential ones.
  • The nightly diagnosis traced the findings to the job's case-sensitive ExtractText().Contains check and its document-level replace. It cited no line numbers, and its proof command targeted a test project that did not exist yet.
  • The refactor kept every rule in a 68-line RebrandJob.cs, and its out/final copies matched out/rebranded.
  • final.csv matched the manifest statuses but marked 118 rows incomplete, and Codex reported the content check as failed.
  • Codex's run and the fresh clone each passed 4 of the 6 tests. The split-run test failed on the signature and the table test on a party-table cell.
  • The API answer listed four load, seven save and five asynchronous members, and named the parameterless Save as the one that overwrites the source.
  • It correctly named four documented members that do not compile. These were the constructor taking bytes and a string, FlushModelToBytes, ReplaceTextAcrossRuns on a paragraph and SetNoChangeStatusAsUpdated.
  • It also listed the bytes-only constructor, which compiles, and missed five others that fail on 2026.10.4, FromMarkdown, FromMarkdownFile, ExportToMarkdownFile, GetMailMergeWorkingBytes and ApplyCellStyle on a table.
  • Some steps ran differently from the page. The image was rendered with python-docx from the run's MSA-1050 copy. The nightly job, its log and the legal review file were added immediately before the investigation prompt.

The GitHub Copilot guide tests a saved statement by reading its table through Tables, Rows and Cells. The Cursor guide lists the parts of a DOCX that IronWord does not read.

The shared run record lists the Codex version, model, machine and timing for this run.


Troubleshooting


Questions?

If you have any questions, reach out to support@ironsoftware.com

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

Read More

Ready to Get Started?

Nuget Downloads 58,016Version:2026.10just released