<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Salaheldinaz</title><link>https://salaheldinaz.com/</link><description>OSINT · Intelligence · Cyber Security · Automation · Technologist · Investigator</description><language>en-US</language><lastBuildDate>Fri, 13 Nov 2026 10:53:46 +0000</lastBuildDate><atom:link href="https://salaheldinaz.com/tags/no-code/" rel="self" type="application/rss+xml"/><item><title>Automating without writing code</title><link>https://salaheldinaz.com/blog/automating-without-writing-code/</link><guid isPermaLink="true">https://salaheldinaz.com/blog/automating-without-writing-code/</guid><pubDate>Fri, 13 Nov 2026 00:00:00 +0000</pubDate><description>A visual workflow tool builds the same watcher an earlier post built in Python, without a line of code — and then an honest account of the 6 places a canvas runs out.</description><category>osint</category><category>automation</category><category>no-code</category><category>methodology</category><content:encoded><![CDATA[<span class="hero-label not-prose">The Analyst&#39;s Stack · Methods</span>

<p>The most common reaction to <a href="https://salaheldinaz.com/blog/building-your-first-automation-workflow/">the post that builds a
watcher</a> is not confusion. It is
agreement followed by a shrug:</p>
<p><em>I understood every word of that and I am still not going to write Python.</em></p>
<p>Fair. So here is the same tool, built the other way: as boxes on a screen, with no code
in it. Then the part that matters more: the specific places where the boxes run out, and
how you will know you have reached one.</p>
<h2 id="a-workflow-is-a-trigger-and-a-chain">A workflow is a trigger and a chain</h2>
<p>A node-based tool <em>(you assemble boxes on a canvas and join them with wires, instead of
typing statements in a file)</em> has exactly 2 concepts, and once you have those, the
whole category makes sense.</p>
<p>I am going to use n8n for the worked example, because it self-hosts and because its node
names describe what they do. Make, Zapier, Power Automate and Node-RED differ in pricing,
in where they run and in what they connect to, and not at all in the 2 concepts below.
If you are reading this with a different canvas open, the box names will change and
nothing else will.</p>
<p>The first is the <strong>trigger</strong>. One box on the left that decides when everything runs: a
clock, an incoming email, a form submission, a message in a channel. Nothing happens
until the trigger fires.</p>
<p>The second is the <strong>chain</strong>. Every box after the trigger takes what the box before it
produced, does one thing to it, and hands the result on. That is the entire model. A
node has no idea what came 2 steps earlier; it sees the output of its immediate
neighbour and nothing else.</p>
<p>What flows along the wire is a list of <strong>items</strong> <em>(one item is one record, whether that
is an entry, a message or a row, carried as a small bundle of labelled fields)</em>. If a node
receives 5 items, it usually runs 5 times, once per item, and passes 5 results
on. That repetition is free and invisible, and it is the reason a chain of 7 boxes can
process a page of 30 entries without anybody writing a loop.</p>
<p>Now put that against the spine. This series has spent 17 posts on 6 stages:
collect, normalize, store, analyze, alert, present. A node-based workflow is not a
different architecture. It is the same 6 stages with a specific node sitting on each
one.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-nodes_light.svg" width="1160" height="190" alt="The 6-stage spine, each stage labelled by the node that performs it: Schedule plus HTTP for collect, HTML for normalize until the parsing gets awkward and then a code node, Remove Duplicates for store, Filter for analyze, Send Message for alert, Append to Sheet for present. The normalize box is highlighted." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-nodes_dark.svg" width="1160" height="190" alt="The 6-stage spine, each stage labelled by the node that performs it: Schedule plus HTTP for collect, HTML for normalize until the parsing gets awkward and then a code node, Remove Duplicates for store, Filter for analyze, Send Message for alert, Append to Sheet for present. The normalize box is highlighted." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>The spine, as nodes.</strong> Of the 6 stages, 5 have a box you configure by filling in fields. Normalize has one only while the page is tidy; past that it takes a code node, and that is the whole second half of this post.</figcaption>
</figure>

<p>Read that diagram twice. The first pass tells you a visual tool covers the same ground a
script does. The second pass tells you where it will eventually stop, because the
highlighted box is the stage where the pre-built nodes run out first.</p>
<h2 id="the-same-watcher-as-boxes">The same watcher, as boxes</h2>
<p>Here is the build. Same problem as the script version — check a page on a schedule,
notice what is new, tell a human — in 7 nodes and no code. I am describing canvas
actions, because that is
what you actually do: add a node, pick an operation from a dropdown, fill in two or
three fields, drag a wire.</p>
<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">1</span>
    <span class="step-title">Schedule Trigger</span>
  </div>
  <div class="step-body">Drop it on an empty canvas. It has one meaningful setting, an interval. Set it to every
15 minutes. This is stage 1&rsquo;s clock, and it is the equivalent of the script&rsquo;s cron line,
except it lives with the workflow instead of on a server somewhere.</div>
</div>

<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">2</span>
    <span class="step-title">HTTP Request</span>
  </div>
  <div class="step-body">Wire it to the trigger. Paste the URL of the page you want to watch. Add one header:
a <code>User-Agent</code> that says what your tool is and gives a way to contact whoever runs it,
such as <code>research-monitor/1.0 (analyst@example.test)</code>. The honesty argument from <a href="https://salaheldinaz.com/blog/building-your-first-automation-workflow/">Building your
first automation workflow</a> applies here
unchanged; the tool does not make you anonymous and you should not want it to.</div>
</div>

<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">3</span>
    <span class="step-title">HTML, then Split Out</span>
  </div>
  <div class="step-body"><p>The HTTP node hands you one item containing the whole page. The HTML node pulls values
out of it with a CSS selector, exactly as BeautifulSoup did: you type <code>article.entry a</code>
into a field rather than into a function call. That still gives you one item holding a
list of 30 links, so Split Out turns that list into 30 items, one per entry.</p>
<p><strong>This is stage 2</strong>, and it is the step people delete because it looks like it does
nothing. Everything downstream counts items. Give it 30, not one.</p>
</div>
</div>

<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">4</span>
    <span class="step-title">Remove Duplicates</span>
  </div>
  <div class="step-body"><p>The node that makes this a tool rather than a toy. It has an operation for discarding
items it has already seen in previous runs, and you tell it which field to compare: the
entry&rsquo;s URL. It keeps its own record of what has passed through, between runs, without
you designing a database.</p>
<p>That is stage 3, and it is the single feature most worth checking for when you are
choosing between tools. A visual tool without cross-run deduplication will send you the
same 96 messages by morning that a first-draft script does.</p>
</div>
</div>

<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">5</span>
    <span class="step-title">Filter</span>
  </div>
  <div class="step-body"><p>One condition: keep the item only if its title contains a term you care about. Anything
failing the condition stops here and the chain below it never runs for that item.</p>
<p>Put the filter <strong>after</strong> the deduplication step, not before it. Order matters in a way
that is easy to miss: filter first and your seen-list only ever contains the items you
cared about, so the day you widen the filter, a month of previously-ignored entries
arrives at once. Deduplicate first and everything the source published is on record,
whether it interested you or not.</p>
</div>
</div>

<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-order_light.svg" width="776" height="272" alt="The same 2 nodes in 2 orders. Remove duplicates, then filter: the seen-list holds everything the source published, so widening the filter later brings only new items. Filter, then remove duplicates: the seen-list holds only what you kept, so widening the filter later releases a month of old entries at once." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-order_dark.svg" width="776" height="272" alt="The same 2 nodes in 2 orders. Remove duplicates, then filter: the seen-list holds everything the source published, so widening the filter later brings only new items. Filter, then remove duplicates: the seen-list holds only what you kept, so widening the filter later releases a month of old entries at once." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>Order is memory.</strong> Whichever node runs first decides what the seen-list remembers.</figcaption>
</figure>

<div class="step not-prose">
  <div class="step-head">
    <span class="step-num">6</span>
    <span class="step-title">Send a message</span>
  </div>
  <div class="step-body">Pick the node for whatever your team actually reads — a chat channel, an email, a row
appended to a spreadsheet. Type the message using references to the fields the previous
nodes produced. Send the link, not the content, for the same reason the script did: you want
to be pulled to the source rather than handed a summary that quietly becomes what you
remember.</div>
</div>

<p>Seven boxes. Press the button that runs the workflow once, watch it go green left to
right, then turn on the schedule. The first run alerts on everything currently on the
page and every run after that is silent until something genuinely new appears. Same
behaviour, and the same first-run burst, as the 60-line script.</p>
<p><img src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-1-canvas_hu_84bd8db7ef9b579a.webp" width="960" height="412" alt="A workflow canvas with 7 boxes in one line, shown here in 2 rows, joined left to right by wires: Schedule Trigger, HTTP Request (labelled GET: https://news.ycombinator.com), HTML (extractHtmlContent), Split Out, Remove Duplicates, Filter and Send message." loading="lazy" decoding="async"></p>
<div class="post-caption not-prose"><strong>The whole program.</strong> It is 7 boxes and 6 wires. A colleague can be told what this does
by being shown it, which is not true of 60 lines of Python. A throwaway demo workflow
watching a public front page; it holds no credentials, and Send message is a placeholder
that sends nothing. On screen the 7 boxes sit in one line; it is split into 2 rows here.</div>

<p><img class="img-narrow" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-2-http-node_hu_4cb008d82d215354.webp" width="425" height="955" alt="The settings panel of the HTTP Request box. Method GET, URL https://news.ycombinator.com/, Authentication None, Send Headers switched on, and one header: Name User-Agent, Value research-monitor/1.0 (analyst@example.test)." loading="lazy" decoding="async"></p>
<div class="post-caption not-prose"><strong>One node, opened.</strong> Every field here has an equivalent line in the script version. The
difference is that this one is discoverable by clicking rather than by reading
documentation. Authentication is set to None: the page is public, so there is no
credential to choose.</div>

<h2 id="what-the-canvas-actually-gives-you">What the canvas actually gives you</h2>
<p>Four things, and none of them is &ldquo;it is easier.&rdquo; Some of this is harder than the script.</p>
<p><strong>There is nothing to install or deploy.</strong> The Python watcher is 60 lines and about six
hours of everything else: a machine that stays awake, Python on it, a scheduler, a place
for the database file, some way of noticing it died. A workflow tool arrives with all of
that already decided. For an analyst whose alternative is &ldquo;ask someone in IT for a
server&rdquo;, that gap is the entire difference between a tool existing and not existing.</p>
<p><strong>The logic is visible.</strong> You can see the whole thing at once. Where a script&rsquo;s control
flow lives in indentation you have to hold in your head, a canvas puts the branch on
screen as 2 wires going to 2 different places.</p>
<p><strong>A colleague can read it without you.</strong> This is the one I would argue hardest for.
Handover is where most analyst-built tooling dies — a script is readable by people who
read scripts, which in most investigative teams is one person, and that person is you.
Seven labelled boxes survive your annual leave.</p>
<p><strong>Credentials are managed, not lying next to the script.</strong> A workflow tool stores an API
key or a chat token in its own credential store, references it by name, and keeps it out
of the thing you export and share. The script version&rsquo;s default outcome is a token typed
directly into the file, which then gets copied to a laptop, pasted into a message to a
colleague, and eventually committed somewhere. That failure has a better safety record
here, and it is a security improvement rather than a convenience one.</p>
<p><img class="img-narrow" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-3-filter-run_hu_4916de5282ea5c09.webp" width="662" height="854" alt="Two panes from one completed run, stacked. Top, the INPUT pane of the Filter box: the boxes upstream of it with their item counts, Remove Duplicates 30 items, Split Out 30 items, HTML 1 item, HTTP Request 1 item, Schedule Trigger 1 item. Bottom, the OUTPUT pane: Kept 9 items and Discarded 21 items, with a table of the 9 kept rows, each a headline title and its link." loading="lazy" decoding="async"></p>
<div class="post-caption not-prose"><strong>One run, opened up.</strong> Click any node after a run and you see exactly what arrived and
exactly what left. This is the best debugging experience in the whole category, and it is
also the thing the limitations below take away from you. Here the Filter box, whose
condition is a title containing &ldquo;to&rdquo;, ignoring case: 30 items in, 9 kept, 21 discarded. The rows are real
headlines from a public front page on the day of the demo run. On screen the 2 panes sit
side by side; they are shown stacked here.</div>

<h2 id="running-it-yourself">Running it yourself</h2>
<p>One decision is worth making deliberately: hosted or your own machine.</p>
<p>Most of these tools offer both. Hosted means somebody else runs it and your collected
material passes through their infrastructure. Self-hosted means the whole thing runs on
a machine you control, and the pages you fetch, the records you keep and the credentials
you store never leave it.</p>
<p>For investigative work the argument is the one made in <a href="https://salaheldinaz.com/blog/opsec-for-automation/">OPSEC for
automation</a> and the same one this series makes about local
models: the question is not whether a vendor is trustworthy, it is how many parties know
what you are interested in. A hosted workflow tool knows every source you watch, how
often you check it, and when your interest in it changed. That is a sensitive profile of
your work even when no single item in it is sensitive.</p>
<p>Self-hosting costs you the operations work you were trying to avoid — a machine, updates,
backups of the workflow definitions. Sometimes that trade is obviously right and
sometimes it obviously is not. Make it on purpose.</p>
<h2 id="where-it-stops-being-enough">Where it stops being enough</h2>
<p>Everything above is real. So is this. Six places the canvas runs out, roughly in the
order you will meet them.</p>
<p><strong>Real state across runs.</strong> Deduplication is the one piece of memory you get for free,
and it answers exactly one question: <em>have I seen this before?</em> The moment you need a
different question (has this item&rsquo;s price changed since Tuesday, is this the third time
this week that name appeared, what did the page look like before today&rsquo;s edit), you need
somewhere to write things and read them back. You can bolt a spreadsheet or a database
node on, and it works, and it is also the point where your 7 clean boxes become
15 boxes with read-modify-write logic spread across them.</p>
<p><strong>Parsing that is not trivial.</strong> A CSS selector covers a well-behaved page. Then you meet
a date written as <code>31 Oct 2026, 14:05 (local)</code>, and a source that puts 2 fields in one
string, and a feed that changes shape depending on the entry type. There is no node for
that, so you add a code node and write something like this inside it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Code node: turn one awkward date field into something sortable.
</span></span></span><span class="line"><span class="cl"><span class="c1">// `items` is whatever the previous node passed in; return the same shape.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="nx">$input</span><span class="p">.</span><span class="nx">all</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MONTHS</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">Jan</span><span class="o">:</span> <span class="s1">&#39;01&#39;</span><span class="p">,</span> <span class="nx">Feb</span><span class="o">:</span> <span class="s1">&#39;02&#39;</span><span class="p">,</span> <span class="nx">Mar</span><span class="o">:</span> <span class="s1">&#39;03&#39;</span><span class="p">,</span> <span class="nx">Apr</span><span class="o">:</span> <span class="s1">&#39;04&#39;</span><span class="p">,</span> <span class="nx">May</span><span class="o">:</span> <span class="s1">&#39;05&#39;</span><span class="p">,</span> <span class="nx">Jun</span><span class="o">:</span> <span class="s1">&#39;06&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                 <span class="nx">Jul</span><span class="o">:</span> <span class="s1">&#39;07&#39;</span><span class="p">,</span> <span class="nx">Aug</span><span class="o">:</span> <span class="s1">&#39;08&#39;</span><span class="p">,</span> <span class="nx">Sep</span><span class="o">:</span> <span class="s1">&#39;09&#39;</span><span class="p">,</span> <span class="nx">Oct</span><span class="o">:</span> <span class="s1">&#39;10&#39;</span><span class="p">,</span> <span class="nx">Nov</span><span class="o">:</span> <span class="s1">&#39;11&#39;</span><span class="p">,</span> <span class="nx">Dec</span><span class="o">:</span> <span class="s1">&#39;12&#39;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">raw</span> <span class="o">=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">json</span><span class="p">.</span><span class="nx">published</span><span class="p">;</span>             <span class="c1">// &#34;31 Oct 2026, 14:05 (local)&#34;
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">m</span> <span class="o">=</span> <span class="nb">String</span><span class="p">(</span><span class="nx">raw</span><span class="p">).</span><span class="nx">match</span><span class="p">(</span><span class="sr">/(\d{1,2}) (\w{3}) (\d{4}), (\d{2}):(\d{2})/</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">m</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">out</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="nx">json</span><span class="o">:</span> <span class="p">{</span> <span class="p">...</span><span class="nx">item</span><span class="p">.</span><span class="nx">json</span><span class="p">,</span> <span class="nx">published_iso</span><span class="o">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">parse_failed</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">day</span> <span class="o">=</span> <span class="nx">m</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">out</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="nx">json</span><span class="o">:</span> <span class="p">{</span> <span class="p">...</span><span class="nx">item</span><span class="p">.</span><span class="nx">json</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">published_iso</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">m</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="si">}</span><span class="sb">-</span><span class="si">${</span><span class="nx">MONTHS</span><span class="p">[</span><span class="nx">m</span><span class="p">[</span><span class="mi">2</span><span class="p">]]</span><span class="si">}</span><span class="sb">-</span><span class="si">${</span><span class="nx">day</span><span class="si">}</span><span class="sb">T</span><span class="si">${</span><span class="nx">m</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="si">}</span><span class="sb">:</span><span class="si">${</span><span class="nx">m</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="si">}</span><span class="sb">`</span> <span class="p">}</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nx">out</span><span class="p">;</span>
</span></span></code></pre></div><p>That is JavaScript. It has a loop, a regular expression, a failure branch and a data
structure. You are programming — in a text box in a browser, with no file on disk, no
search across your other workflows, no linter and nothing keeping a history of what it
said last week. The no-code promise ends at this box, and it ends quietly, one small
reasonable edit at a time.</p>
<p><strong>Testing.</strong> There is no test suite for a canvas. You cannot write &ldquo;given this page,
assert 3 items come out with these IDs&rdquo; and run it after every change. What you have
instead is running the workflow and looking at it, which is fine for 7 nodes and is
not fine for the 15-node version you will have in 4 months. The script version&rsquo;s
failure section — alarm on zero, log what you parsed, send a heartbeat — is harder here, not
easier, because each of those defences is another node rather than another line.</p>
<p><strong>Review and diffing.</strong> A workflow is stored as JSON <em>(a structured text format that
machines read easily and people read badly)</em>. You can put it in version control, and you
should, and the diff will be several hundred changed lines because you dragged a box
4 pixels to the left. A reviewer cannot see what changed. In practice, teams stop
reviewing workflow changes, which means a visual tool&rsquo;s greatest strength, that the
logic is legible, does not extend to the logic&rsquo;s <em>history</em>.</p>
<p><strong>Debugging a run that failed 3 days ago.</strong> The execution view is excellent while
the run is in front of you. Execution history is finite: it gets trimmed by age or
count, and it is the first thing turned down when storage gets tight. A script that
appends to a log file has a worse debugger and a much better memory. Decide early what
you want kept, because the default is usually &ldquo;not that much.&rdquo;</p>
<p><strong>Portability.</strong> Your work exists as that JSON, in that tool&rsquo;s schema, referencing that
tool&rsquo;s node types. Moving to a different visual tool is a rebuild, not an export. That
is not a reason to avoid one — a script has lock-in too, to a language and a set of
libraries — but it is a reason to expect the rebuild rather than be surprised by it.</p>
<p><img class="img-narrow" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-4-code-node_hu_e9bc932de7a344e4.webp" width="626" height="790" alt="The settings panel of a code box. Mode Run Once for All Items, Language JavaScript, and an editor showing lines 1 to 19 of a short script that matches a date written like “31 Oct 2026, 14:05 (local)”, maps the month name to a number, and adds a sortable published_iso field to each item, or a parse_failed flag when the pattern does not match." loading="lazy" decoding="async"></p>
<div class="post-caption not-prose"><strong>The honest limit, on screen.</strong> The moment a code node appears, you have a program with
a worse editor and no version history. That is not a reason to stop. It is a reason to
notice. The code box&rsquo;s settings panel from a variant of the demo workflow, where the box
sits in the middle of the chain.</div>

<div class="limitation not-prose"><p class="limitation-title">What a visual tool cannot do for you</p><div class="limitation-body"><p>It cannot show you what somebody changed. The workflow is stored as JSON, so a workflow
diff is not a readable change: dragging a box 4 pixels to the left rewrites several
hundred lines, and quietly widening the filter that decides what reaches a human rewrites
a handful of lines buried in the middle of them. Both look the same in review, so in
practice nobody reviews either.</p>
<p>That matters more here than it would elsewhere, because the canvas is the thing you were
going to hand to a colleague. You can hand them the workflow as it stands today. You
cannot hand them the question they will actually ask, which is what it was doing on the
day it produced the thing now sitting in a published piece. A 60-line script answers
that in a diff somebody can read in a minute.</p>
</div>
</div>

<h2 id="the-tool-tells-you-when-to-leave">The tool tells you when to leave</h2>
<p>Start here. That is the recommendation, and it is not a compromise recommendation.</p>
<p>Most analysts&rsquo; real automation problems are genuinely small: watch a thing, notice a
change, tell someone, put it in a sheet. Those fit entirely inside a visual tool, they
fit today rather than after a month of learning Python, and they keep working while you
are on leave because somebody else can read them. If you do decide to learn Python
later, <a href="https://salaheldinaz.com/blog/building-your-first-automation-workflow/">the first workflow post</a> lists
the beginner courses I would start with.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-ladder_light.svg" width="776" height="190" alt="A workflow growing in 4 steps: 7 nodes and no code, then one code node, then 3 code nodes, then moving to code. The 3-code-node step is highlighted as the point where the decision has already been made." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/automating-without-writing-code/images/m1-ladder_dark.svg" width="776" height="190" alt="A workflow growing in 4 steps: 7 nodes and no code, then one code node, then 3 code nodes, then moving to code. The 3-code-node step is highlighted as the point where the decision has already been made." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>Nobody decides to leave.</strong> You arrive at the third box and discover you left a while ago.</figcaption>
</figure>

<p>The useful part is that you do not have to make the build-or-code decision in advance,
which is good, because in advance you have no information. The workflow makes it for
you, and it announces itself the same way every time: <strong>count your code nodes.</strong> Zero is
the tool working as intended. One is fine, and worth noting the date. Three means you
are maintaining a program in a web form, and the honest move is to admit it and move
that part into a file where a diff is readable and a test can exist.</p>
<p>Moving is not starting over, either. The 6 stages do not change. The thinking you did
about stable identifiers and what the filter should keep survives the port intact,
because <a href="https://salaheldinaz.com/blog/what-automation-means-for-an-osint-analyst/">that thinking was never about the
language</a>. Only the typing changes.</p>
]]></content:encoded></item></channel></rss>