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; itsn8nfolder holds the workflow filep2-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'sREADME.mdlists every file, andSHA256SUMSlets 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,statusandbrief. 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
- 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.
- Open the workflow menu (… next to the workflow name), point to
Import, choose From file, and pick
p2-approved-briefs-markdown.workflow.jsonfrom the kit'sn8nfolder.
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
- Double-click the File paths node.
- Set
input_fileto the full path of your CSV, for example/home/node/.n8n-files/p2/input/calendar.csv, the value stored in the file. - Set
output_folderto the existing output folder, for example/home/node/.n8n-files/p2/output, also the stored value. - 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 examplevalid_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:
- 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.
- Double-click the Write output file node.
- In the OUTPUT panel on the right, select Binary if it is not
already selected. The card
datashows the file name, the extension (mdorjson), the MIME type (text/markdownorapplication/json) and the size. - 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.
- 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
ownercolumn 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`Bwas written as``A`B``. - The brief follows its own
### Briefheading. 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## Titleis 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<!--,<?,<!DOCTYPEor<![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/adefines a link for the whole file.[guide]in one brief becomes a link if any other brief definesguide, 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
approvedis recognized. Other states, such asreadyorscheduled, 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/nodeand 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.mdandRESULTS.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 withn8n execute --id=p2ApprovedBriefsMd01once per test file, withN8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-filesset. Each of the 21 synthetic test files (9 valid, 12 invalid) was copied in ascalendar.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.
- S1: Use the command line
- S2: Read/Write Files from Disk
- S3: Security environment variables
- S4: n8n v2.0 breaking changes
- S5: Install with npm
- S6: CommonMark Spec, version 0.31.2
Disclosure
Created with AI assistance. Tested with synthetic CSV files in the environment described above.