All guides

Turn approved content calendar rows into Markdown briefs with n8n

For
Solo marketers and small content teams who self-host n8n
Problem
Turn only the approved rows of a content calendar CSV into writer briefs in Markdown, with every campaign ID and every brief kept exactly as written
Tested with
n8n 2.42.7, self-hosted with npm, on Node.js 24.21.0: in the editor and from the command line
Tested on
Data
synthetic test files only

This workflow reads one content calendar CSV and writes one Markdown file of content briefs. Each row whose status is approved becomes one section, in the order of the CSV, with the title, campaign ID, publish date, channel and the writer's brief. Every campaign ID is copied exactly, so 0042, 42 and CAFÉ-01 stay distinct. Each brief keeps its own Markdown and whitespace. Rows with any other status are skipped and listed by number. If anything in the file breaks a rule, the workflow writes a JSON error report instead, and no Markdown at all.

There is no AI step. The same CSV always gives the same Markdown bytes, and the run time appears only in the file name.

Tested: n8n 2.42.7, self-hosted and installed with npm, on Node.js 24.21.0 on Linux, with synthetic CSV files and the same workflow file that is in our kit. In the editor, on 2026-10-26: three valid test files, one invalid one and two wrong paths. From the command line, on 2026-10-13: all 21 test files, twice. AI agents ran every test through scripts; in the editor test a script operated the editor in a headless browser. No person has followed these steps.

Not tested: a person following these steps; Docker; n8n Cloud; other n8n or Node.js versions; Windows or macOS; desktop browsers; an instance that already has other workflows; 17 of the 21 test files in the editor (they ran only from the command line); a copy of the workflow file with edited paths; a missing or read-only output folder; real calendar exports; and how any Markdown app displays the result (see Exact bytes, conditional display).

Before you start

  • A self-hosted n8n that you can open in a browser. We tested only n8n 2.42.7 installed with npm, on Node.js 24.21.0, signed in as the owner of a new, empty instance. That n8n version itself requires Node.js 24 or later (its package declares node >=24.0.0), although the n8n installation page lists Node.js 20.19 to 24.x in general (S5).
  • Access to the files of the machine that runs n8n, as the user that runs n8n. You put the CSV where n8n may read it (Step 1). The workflow reads and writes files there, not on the computer with your browser. You can download the result through the editor (Step 5).
  • Not tested in Docker or on n8n Cloud. In Docker, file paths refer to the container's file system (S2).
  • The kit, p2-approved-briefs-markdown-v0.1.0.zip, from our free sample page. Unzip it; its n8n folder holds the workflow file p2-approved-briefs-markdown.workflow.json. The workflow inside is named "P2 Approved calendar rows to Markdown briefs (contract 0.2.1)". The kit also holds the 21 synthetic test files and the results we expected for each, so you can try the workflow before you use your own calendar. The workflow file is byte for byte the file in all our tests. The kit's README.md lists every file, and SHA256SUMS lets you check them. The Tracetern-original material in the kit is under the MIT License.
  • Your CSV, UTF-8, comma-separated, with a header row that contains these six column names, spelled exactly like this: campaign_id, publish_date, channel, title, status and brief. Other columns may be present; they are not written to the briefs.

What the CSV must contain

Below, trimmed means that spaces, tabs, line breaks and no-break spaces are removed from the start and end of a cell. Header names are trimmed, then matched exactly, including case. Blank rows are ignored.

Column Rule
campaign_id Required in every row and unique across all rows, whatever their status: 0042 in an approved row and 0042 in a draft row is a duplicate. Copied exactly and never trimmed, so it must not start or end with a space, tab, line break or no-break space. In approved rows it must not contain a line break.
status A row is included only when its trimmed status is exactly approved, in lowercase. Approved, APPROVED, ready and approved. are skipped.
publish_date In approved rows, trimmed, a real calendar date written as YYYY-MM-DD, such as 2026-11-03.
title, channel In approved rows, trimmed, not empty and without line breaks inside. Characters such as *, [ and # are escaped so that a CommonMark app displays them as typed (see Exact bytes, conditional display).
brief In approved rows, not empty: it must contain something other than spaces, tabs, line breaks and no-break spaces. It is written as Markdown, byte for byte, except that line endings become LF (see Read the briefs).

Rows that are not approved are not checked for date, title, channel or brief. Each cell may hold up to 131,072 characters (Unicode code points). Every header name, including those of extra columns, must be non-empty and unique. In approved rows, campaign_id, title and channel must not contain a NUL character (U+0000), and title and channel must not start or end with an invisible space-like character, such as an ideographic space, that trimming does not remove.

This is the first test file, valid_basic.csv. Record 6 has the status approved with spaces; it counts as approved. Record 5 is blank.

campaign_id,publish_date,channel,title,status,brief,owner
0042,2026-11-03,email,Weekly notes,approved,"Three tips for planning December posts.
Link the calendar template.",team-a
42, 2026-11-04 , linkedin ,Behind the *scenes*,approved,Show how the team reviews drafts.,team-b
0107,2026-11-05,blog,Q4 recap,draft,Not ready yet.,team-a
,,,,,,
CAFÉ-01,2026-11-06,email,Café launch [beta] #1, approved ,Announce the beta menu.,team-c

Record numbers count CSV records, not lines. The header is record 1. A line break inside a quoted cell does not start a new record, so the two-line brief above is still record 2.

Step 1: Put the CSV where n8n may read it

Since n8n 2.0, the Read/Write Files from Disk node may by default use only the .n8n-files folder in the home folder of the user that runs n8n (S2, S4). Create the folders if they do not exist.

On the machine that runs n8n, signed in as the user that runs n8n:

mkdir -p ~/.n8n-files/p2/input ~/.n8n-files/p2/output
cp calendar.csv ~/.n8n-files/p2/input/calendar.csv

n8n must be allowed to write to the output folder. To try the kit's test files first, copy them from the kit's fixtures/input folder into the input folder on the machine that runs n8n, the same way.

In our editor test, n8n ran with the home folder /home/node and its default file access setting. The test files were in /home/node/.n8n-files/p2/input and the output folder was /home/node/.n8n-files/p2/output. Our test script prepared these folders; the commands above were not run as shown. A file outside .n8n-files was refused (see Troubleshooting).

Step 2: Import the workflow

  1. Open a new, empty workflow. In our test, the new instance showed an "n8n Assistant" page after the owner account was created; our test script chose Set up later in Settings, then opened Overview (the house icon in the left sidebar). That page offered a Build a workflow card, which opened a new workflow. With existing workflows, the entry point may look different; we did not test that.
  2. Open the workflow menu (… next to the workflow name), point to Import, choose From file, and pick p2-approved-briefs-markdown.workflow.json from the kit's n8n folder.

The canvas then shows "P2 Approved calendar rows to Markdown briefs (contract 0.2.1)": eight nodes and a note. The note still says "Private pilot (Tracetern P2)" and names a contract file from our repository, because the workflow file is unchanged from the one we tested; the kit's README.md explains it.

In n8n 2.42.7 the workflow saved itself; there was no Save button. Our test did not click Publish; manual runs worked without it.

Step 3: Set the two paths

  1. Double-click the File paths node.
  2. Set input_file to the full path of your CSV, for example /home/node/.n8n-files/p2/input/calendar.csv, the value stored in the file.
  3. Set output_folder to the existing output folder, for example /home/node/.n8n-files/p2/output, also the stored value.
  4. Press Tab to leave the field, then close the panel with × at the top right.

Our test changed input_file for each test file, for example to /home/node/.n8n-files/p2/input/valid_basic.csv, and kept output_folder.

Use plain absolute paths. In the input path, *, ? and [ ] are pattern characters (S2), so the path must not contain them. The output folder must already exist.

Step 4: Run it

Click Execute workflow at the bottom of the canvas. One of three things happens:

  • The file is accepted. The nodes up to Input rejected? turn green and n8n shows "Workflow executed successfully". One new file appears in output_folder, named <file name without .csv>.briefs.<UTC time>-e<execution ID>.md, for example valid_basic.briefs.20261026T182003Z-e1.md.
  • The file is rejected (a data problem). The run stops at Stop: input rejected. n8n shows "Problem in node ‘Stop: input rejected‘" with a message such as "Input rejected (E_DUPLICATE_ID). No Markdown was written. See /home/node/.n8n-files/p2/output/invalid_duplicate_ids.errors.20261026T182031Z-e4.json". The one new file is that error report, <file name without .csv>.errors.<UTC time>-e<execution ID>.json. No Markdown file is written.
  • The workflow could not read the file (a setup problem). The run stops at Read calendar file, and n8n shows "Problem in node ‘Read calendar file‘". Nothing is written, not even an error report. See Troubleshooting.

Each run that reads the CSV writes a new file whose name includes the time and the execution ID, so earlier results are not overwritten; in our test, four runs wrote four separate files into the same output folder. The input file is not changed. The Executions tab lists every run and shows the ID of the selected one, for example "ID#6".

Step 5: Download the result

Right after the run, with the workflow still open:

  1. If the notification is still shown at the bottom right, close it with its ×; it can cover the panel that opens next. Our test script did this before each download.
  2. Double-click the Write output file node.
  3. In the OUTPUT panel on the right, select Binary if it is not already selected. The card data shows the file name, the extension (md or json), the MIME type (text/markdown or application/json) and the size.
  4. Click Download in that OUTPUT panel. The INPUT panel on the left shows the same file with its own Download button; we tested the one in the OUTPUT panel.
  5. Close the panel with ×.

In our test, the browser received each file under the name it has in output_folder, with the same bytes. Where the file is saved depends on your browser's settings; our test used a headless browser, which has no download bar and no "Save as" dialog.

This also works for a rejected file. The run stops at Stop: input rejected, but Write output file has already written the error report, and its OUTPUT panel offers it for download.

The download is the copy of the file that n8n stored with the run. The file also stays in output_folder on the machine that runs n8n. If the run stopped at Read calendar file, there is nothing to download. Our test used Download only right after each run; not after reloading the page, not from the Executions tab and not with the View button.

Read the briefs

For valid_basic.csv, the workflow wrote this file (541 bytes) in the editor and on the command line. It is byte for byte the expected output that we wrote by hand before the workflow existed, expected/valid_basic.output.md in the kit:

# Content briefs

- Approved record count: 3
- Skipped record numbers (status is not "approved"): 4

## Weekly notes

- Campaign ID: `0042`
- Publish date: 2026-11-03
- Channel: email

### Brief

Three tips for planning December posts.
Link the calendar template.

## Behind the \*scenes\*

- Campaign ID: `42`
- Publish date: 2026-11-04
- Channel: linkedin

### Brief

Show how the team reviews drafts.

## Café launch \[beta\] \#1

- Campaign ID: `CAFÉ-01`
- Publish date: 2026-11-06
- Channel: email

### Brief

Announce the beta menu.

What to look for:

  • The summary counts the approved rows and lists the record numbers of skipped rows (here record 4, a draft), or says none. Blank rows are neither counted nor listed. If a row you expected is missing, look for its record number here.
  • One section per approved row, in the order of the CSV. The owner column is not written.
  • Titles and channels are escaped with backslashes: these 11 characters get one backslash each: \ ` * _ [ ] < > # & ~. *scenes* becomes \*scenes\*, so a CommonMark app displays it as typed instead of in italics.
  • Campaign IDs are written in a code span, between backticks. An ID that contains backticks gets a longer fence, so it is still copied exactly: in our tests, the ID A`B was written as ``A`B``.
  • The brief follows its own ### Brief heading. It is the text from the CSV, byte for byte, with one change: Windows (CRLF) and old Mac (CR) line endings become LF. Indentation, blank lines, trailing spaces and no-break spaces stay. Text inside a brief that looks like - Campaign ID: or ## Title is part of the brief; the workflow never reads it as a field.

Apart from rejecting an empty brief, the workflow does not edit, check or improve the briefs.

Check a result against the kit

The Markdown has no run time inside, so for a test file from the kit the result must equal its expected file byte for byte. On Linux or macOS, copy the result into the kit folder, then run this in the kit folder:

cmp expected/valid_basic.output.md valid_basic.briefs.20261026T182003Z-e1.md

valid_basic.briefs.20261026T182003Z-e1.md is the name of the result in our editor test; use the name of your own file, which has the time and the execution ID of your run. No output means the two files are identical. Do not open and re-save the result in an editor first: some editors change trailing spaces, tabs or line endings, which several test files check on purpose. For a rejected test file, compare each entry in errors with expected_errors in expected/<file name>.json: the code and the detail fields must match, in the same order; the message text is not part of the expectation. We ran cmp only on Linux, on our downloaded files.

Exact bytes, conditional display

The workflow guarantees the bytes of the file. How a Markdown app displays the file depends on that app and on what the briefs contain.

Always true, for every accepted CSV: the layout above; UTF-8 without a byte order mark; LF line endings only; every generated line in its place, in the order of the CSV; every brief byte for byte as described above. Two runs on the same CSV give identical files. In our command-line test, all 9 accepted files were byte for byte the expected outputs, in both runs, and so were the 3 accepted files of the editor test.

True only under conditions: that each heading, metadata list and brief also displays as intended. We checked this only with commonmark.js 0.31.2, a parser for CommonMark 0.31.2 without extensions, which turned each output into a syntax tree. We did not produce HTML and did not open the files in any app. The conditions:

  • The app follows CommonMark 0.31.2 without extensions. Apps with extensions, such as tables or automatic links, may display some characters differently. We did not test GitHub, Obsidian, Notion, Google Docs, VS Code or a website generator.
  • No brief ends inside an open code fence or HTML block. A code fence (``` or ~~~) without a closing fence continues to the end of the file, and so does an HTML block that is never closed, such as one starting with <pre, <script, <style, <textarea, an HTML comment <!--, <?, <!DOCTYPE or <![CDATA[ (S6). Every section after that brief, its headings and metadata included, then displays as part of it. The bytes are still correct.
  • No brief uses or defines a link label that another brief defines. A line such as [guide]: https://example.com/a defines a link for the whole file. [guide] in one brief becomes a link if any other brief defines guide, and if two briefs define it, the first definition wins in both (S6).

The briefs are not isolated from each other. A heading inside a brief keeps its level and can look like a generated heading; the ### Brief heading marks where each brief starts. HTML inside a brief is passed through, and apps keep, escape or remove it depending on their settings. A NUL character (U+0000) inside a brief stays in the file but displays as the replacement character � (S6).

The workflow does not look for these cases. If a brief displays wrongly, check it for an unclosed code fence or HTML block first.

Run it on the original export, not after the cleanup workflow

Our cleanup workflow keeps extra columns, so you could run a calendar through it first and then through this workflow. If your briefs depend on whitespace, do not. The cleanup workflow trims spaces, tabs, line breaks and no-break spaces from the start and end of every cell except campaign_id, and that includes brief.

A brief can then arrive shorter: the indentation of its first line, blank lines at its start or end, and spaces or no-break spaces at its very start or end are gone. Its Markdown can change meaning. For example, our test file valid_indented_markdown.csv has a brief that starts with two lines indented by four spaces, a code block in Markdown:

    utm_source=newsletter&utm_medium=email
    utm_campaign=december-notes

This workflow keeps both lines as a code block. After the cleanup trim, the first line loses its four spaces, and the two lines become one paragraph of ordinary text. A list indented by two spaces can likewise change its nesting. Line breaks inside a brief are kept by both workflows.

The cleanup workflow also removes blank rows. After it, the record numbers in this workflow's summary and error reports refer to the cleaned file, not to your original export.

We have not run the two workflows one after the other. This follows from their documented rules; we confirmed the code block example with the same CommonMark parser as above. To keep every brief exactly, run this workflow on the original export.

When the file is rejected

Rejection covers the whole file: no Markdown is written, not even for the valid rows. The JSON report lists each problem with its code and where it is. For our test file with a duplicate ID, the editor run wrote this report (402 bytes); the command-line run wrote the same, except that it read the file as calendar.csv:

{
  "status": "error",
  "contract_version": "0.2.1",
  "input_file_name": "invalid_duplicate_ids.csv",
  "errors": [
    {
      "code": "E_DUPLICATE_ID",
      "campaign_id": "0042",
      "records": [
        2,
        4
      ],
      "message": "campaign_id \"0042\" appears in records 2, 4, whatever their status. Duplicates are never dropped or merged; resolve them in the source."
    }
  ]
}

Record 2 was approved and record 4 was a draft. The duplicate still rejects the file.

The checks run in stages, and the report lists the problems of the first stage that fails: the file encoding, then the CSV structure (only the first problem is reported), then the header, then all rows (field count and campaign IDs), then the approved rows (campaign IDs, dates, titles, channels, briefs). After you fix the reported problems, the same or a later stage can report new ones.

Code Meaning What to do
E_EMPTY_FILE * The file has no content, or only a byte order mark Export the calendar again
E_ENCODING The file is not valid UTF-8; byte_offset shows where Export or save as UTF-8
E_MALFORMED_CSV The CSV cannot be read strictly, for example text after a closing quote Fix the quoting in the reported record
E_FIELD_TOO_LONG * A cell has more than 131,072 characters Shorten the cell
E_HEADER_INVALID * A header name is empty or repeated Rename the columns
E_MISSING_COLUMNS One or more of the six required columns is missing Add or rename the columns listed
E_FIELD_COUNT A row has more or fewer cells than the header, often an unquoted comma Quote the cell that contains the comma
E_ID_EMPTY A row has no campaign_id Add the ID
E_ID_WHITESPACE A campaign_id starts or ends with a space, tab, line break or no-break space Remove it in the source; IDs are never trimmed
E_DUPLICATE_ID The same campaign_id appears in two or more rows, whatever their status Resolve the duplicate in the source
E_NO_DATA_RECORDS The file has a header but no rows, or only blank rows Check the export
E_NO_APPROVED_RECORDS No row has the status approved Check the status values, including case
E_DATE_INVALID An approved row's date is not a real YYYY-MM-DD date, for example 11/03/2026, 2026-2-3 or 2026-02-30 Write the date as YYYY-MM-DD
E_FIELD_EMPTY An approved row's title or channel is empty Fill it in
E_BRIEF_EMPTY An approved row's brief has only spaces, tabs, line breaks or no-break spaces Write the brief, or change the status
E_LINE_BREAK An approved row's campaign_id, title or channel contains a line break (for title and channel, line breaks at the start or end are trimmed and allowed) Remove the line break
E_NUL_CHARACTER An approved row's campaign_id, title or channel contains a NUL character (U+0000) Remove it; it cannot display as written
E_EDGE_WHITESPACE An approved row's title or channel starts or ends with an invisible space-like character, such as an ideographic or zero-width no-break space Remove it; usually copy-paste noise

* Not triggered by our test files. The other 15 codes were each triggered by at least one test file, with the expected details.

The workflow never discards an invalid row, merges duplicates or changes an ID to make a file pass. Fix the source data, export it again and run the workflow again.

From the command line instead

The same workflow file also runs without the editor, through the n8n command on the machine that runs n8n. We tested this on 2026-10-13 with the file unchanged, so with its two stored paths: the CSV must then be /home/node/.n8n-files/p2/input/calendar.csv and the output folder /home/node/.n8n-files/p2/output. Each test file was copied in as calendar.csv. If your file names or your n8n user's home folder differ, replace the two values with your own full paths before you import the file. Each appears exactly once in the JSON. We did not test an edited copy.

As the user that runs n8n, in the folder that holds the workflow file (our test gave the file's full path):

n8n import:workflow --input=p2-approved-briefs-markdown.workflow.json
n8n execute --id=p2ApprovedBriefsMd01

The import printed "Successfully imported 1 workflow." and ended with exit code 0. n8n keeps the ID p2ApprovedBriefsMd01 from the file, and an import replaces a workflow that already has that ID (S1). The n8n documentation writes the second command as n8n execute --id <ID> (S1); we tested only the form above.

A workflow imported through the editor gets a new ID instead: in our editor test it was not p2ApprovedBriefsMd01. So run n8n import:workflow before n8n execute --id=p2ApprovedBriefsMd01; we did not test mixing the editor and the command line on one instance.

  • Accepted file: "Execution was successful:", the run data as JSON and exit code 0; the new file is named, for example, calendar.briefs.20261013T110842Z-e1.md. The printed run data contains your briefs (base64-encoded) and your file paths, so treat that terminal output like the briefs themselves.
  • Rejected file: the Stop message, such as "Input rejected (E_DUPLICATE_ID). No Markdown was written. See /home/node/.n8n-files/p2/output/calendar.errors.20261013T110940Z-e14.json", then "Execution was NOT successful. See log message for details." and exit code 1. No Markdown file is written.

The n8n command must use the same settings as your instance, such as its database and user folder; otherwise it may import into a different, new database. Our test had no n8n server running and gave each command its settings explicitly, including N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-files. Among its start-up messages, n8n execute started its own task broker on 127.0.0.1, port 5679, and reported that a Python task runner could not start; neither affected the result. A running n8n server may already use that port. The n8n documentation says most of these commands work even when n8n is not running (S1); we did not test them next to a running server. In Docker the command line runs inside the container (S1); we did not test Docker.

Troubleshooting

There are two kinds of failure.

  • The workflow could not run on your file. These are setup problems. Nothing is written.
  • The workflow ran and rejected your file. These are data problems. The run writes an error report but no Markdown (see above).
Message Meaning What to do
"Problem in node ‘Read calendar file‘" with "No file(s) found" † input_file does not point to an existing file. Nothing was written Check the path, file name and folder
"Problem in node ‘Read calendar file‘" with "Access to the file is not allowed." † The file is outside the folders n8n may use (~/.n8n-files by default). Nothing was written Move the file into ~/.n8n-files. Widening N8N_RESTRICT_FILE_ACCESS_TO (S3) lets file nodes in every workflow on the instance reach more of the machine, so treat that as a security decision
"Set input_file and output_folder in the "File paths" node." ‡ output_folder is empty or only / (an empty input_file probably fails earlier, at Read calendar file) Set both values (Step 3)
"Expected exactly one input file; the read node returned …" ‡ The input path matched several files, usually because of a pattern character Use one plain file path
"The read node returned …, not …. Use a plain file path without wildcards." ‡ The file that was read is not the one named in input_file Use one plain file path
"The output path would overwrite the input file; choose another output_folder." ‡ The output file would replace the input Use a separate output folder
"Problem in node ‘Stop: input rejected‘" with "Input rejected (E_…). No Markdown was written. See …" † The CSV broke a rule Open the .errors…json file it names (see above)
No error, but an approved row is missing from the briefs Its status is not exactly approved Find its record number in the summary's skipped list and fix the status
An "n8n Assistant" page appears after you create the account Part of first-run setup in n8n 2.42.7 To skip it, select Set up later in Settings

† As the editor showed it in our test. On the command line, the rejection printed the "Input rejected …" message without the "Problem in node" title (see From the command line instead); the two read errors were not triggered there, so we have not seen how the command line prints them.

‡ From the workflow's own checks. Not triggered in our tests.

Not tested, so we cannot show the messages: an output folder that does not exist or that n8n may not write to (the run should fail at Write output file, with nothing written); on the command line, n8n execute not finding the workflow, for example after an import into a different database; and a Node.js version that this n8n version does not accept.

What our editor test produced

Input (synthetic, from the kit) Execution What happened Written to output_folder and downloaded
valid_basic.csv 1 "Workflow executed successfully" valid_basic.briefs.20261026T182003Z-e1.md (541 bytes), equal to the expected output
valid_indented_markdown.csv (briefs with indented code and lists) 2 "Workflow executed successfully" valid_indented_markdown.briefs.20261026T182013Z-e2.md (858 bytes), equal to the expected output
valid_trailing_spaces.csv (trailing spaces, no-break spaces, blank lines) 3 "Workflow executed successfully" valid_trailing_spaces.briefs.20261026T182022Z-e3.md (582 bytes), equal to the expected output
invalid_duplicate_ids.csv 4 Rejected at "Stop: input rejected" (E_DUPLICATE_ID) invalid_duplicate_ids.errors.20261026T182031Z-e4.json (402 bytes); no Markdown
A path to a file that does not exist 5 Error in "Read calendar file": "No file(s) found" Nothing
A file outside ~/.n8n-files 6 Error in "Read calendar file": "Access to the file is not allowed." Nothing

Each download had the same name and bytes as the written file. A separate checker script, which is not part of the workflow, judged the four written files and the four downloads against the contract, including the parsed structure of the Markdown; all passed. From the command line, all 21 test files gave the expected results in both runs (see How we tested).

Limits

  • One CSV per run. Comma-separated values, UTF-8 only. Each cell may hold up to 131,072 characters.
  • Only the status approved is recognized. Other states, such as ready or scheduled, and translated status values are skipped.
  • Apart from rejecting an empty brief, the workflow does not write, check or fix briefs. Briefs are not isolated from each other, and HTML in them is not removed.
  • The empty-brief check knows only spaces, tabs, line breaks and no-break spaces. A brief made only of other invisible characters, such as a zero-width space, is accepted.
  • Escaping in titles and channels is designed for CommonMark 0.31.2. Apps with extensions may treat characters that are not escaped differently.
  • Tested only as described at the top, and only with synthetic files.

How we tested

  • AI agents ran these tests, not a person. No person has followed these steps or checked the results by hand. In this guide, "we" means Tracetern.
  • Common setup, on Linux: Node.js 24.21.0 came from the official release archive, with its checksum and signature verified. n8n 2.42.7 was installed with npm from a pinned lock file, without running package install scripts except for two native modules, and not globally. n8n ran as an unprivileged user with the home folder /home/node and no internet access, with a SQLite database and only the node types this workflow uses enabled. Each run used a new, empty n8n.
  • Editor test, 2026-10-26: a Playwright script operated the editor in headless Chromium 153 on a new instance, signed in as its owner, and took Steps 2 to 5 through the editor's own menus, panels and buttons. n8n used its default file access setting. The workflow file came from the ZIP of a pre-release build of the kit; that build differs from the kit described in this guide only in the wording of its README.md and RESULTS.md, and so in its checksum file. The input and output folders were prepared ahead of time with the layout from Step 1. The written files were copied out of the test environment; each was compared byte for byte with the expected file and with its download, and judged by the checker described below.
  • A first recorded editor run that day was judged failed by our test script, because one of the script's own checks was wrong: it did not allow for default values that n8n stores with each run. Its results were identical to those above. We corrected the check and ran the editor test again; the table above shows that run.
  • Command-line test, 2026-10-13: the workflow file was imported unchanged with n8n import:workflow, then run with n8n execute --id=p2ApprovedBriefsMd01 once per test file, with N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-files set. Each of the 21 synthetic test files (9 valid, 12 invalid) was copied in as calendar.csv, with an empty output folder. We ran all 21 files twice, each run with a new n8n database. The 9 valid files gave Markdown byte for byte equal to outputs we had written by hand from the rules before the workflow existed. The 12 invalid files were rejected with one report each, with the expected codes and details, and no Markdown. Both runs gave identical bytes.
  • A separate checker script, which is not part of the workflow, judged every result: the bytes, and for Markdown also the structure parsed with commonmark.js 0.31.2. The only display exception it reported was the expected one: a NUL character in a brief, which displays as �.
  • Before writing and revising this guide, on 2026-10-26 and 2026-10-27, we confirmed that the kit's workflow file, test files and expected outputs are byte for byte those of both tests. We did not run n8n again for this revision.
  • The run records are kept privately and are not part of this article.

Sources

On 2026-10-26 we read the n8n pages below in the Markdown versions they link to, and the CommonMark specification text.

Disclosure

Created with AI assistance. Tested with synthetic CSV files in the environment described above.