📞 Phase 1: Telemetry Isolation via Temporal Filters

When a container boot-loops or crashes under a proxy layer, your terminal screen quickly fills with stale upstream proxy connection errors. Tailing the general service output is noisy and deceptive.

To isolate the failure, we applied a strict ISO-8601 temporal filter directly on the Northflank deployment log stream:

northflank get service logs \
  --projectId lithan \
  --serviceId tastelocal-app \
  --startTime 2026-07-11T04:13:00Z

This skipped thousands of lines of repeating Envoy resets and revealed the exact compile-time parse error thrown by SCI on boot:

Message:  Unmatched delimiter: ), expected: ] to match [ at [12 7]
Data:     {:type :sci.error/parse, :line 111, :column 98,
           :edamame/opened-delimiter "[", :edamame/opened-delimiter-loc {:row 12, :col 7},
           :edamame/expected-delimiter "]", :phase "parse",
           :file "/app/full-stack-app/src/tastelocal/views/about.clj"}

Our Clojure view was crashing. But where? And why did standard editor linting not throw an obvious warning?


🪺 Phase 2: The Odyssey of the Unclosed Vector

In Clojure, HTML layouts are commonly represented as nested vectors via the Hiccup template engine:

(defn render [req]
  (layout/base
    {:title "About"}
    [:div.container
      [:div.card
        [:h1 "Hiring Credentials"]
        ;; ... credentials table ...
        ]]))

During a hotfix iteration to insert credentials and operational documentation into the /about view, the closing delimiters were reformatted. On line 85, a closing block of brackets was modified from ]]]]] to ]]]] to accommodate a new sibling operational block:

[:td {:style "padding: 0.85rem;"} "Moderate reviews"]]]] ;; closed the table, but left the main card open!

This left the parent [:div.card] and [:div.container] vectors unclosed, and when we reached the end of the file on line 111:

[:i.fas.fa-sign-in-alt] "Sign In"]]])))

The Babashka reader was hit with three closing parentheses ))) while the vector nesting stack was still expecting two closing brackets ]]. The parser panicked immediately.


🛡️ Phase 3: Solving it Deterministically with a String-Aware Parser

When you are fighting nested brackets under the pressure of production downtime, trial-and-error editing is a high-entropy trap. Adding a bracket here or moving a parenthesis there only pollutes your git history and risks compounding the compilation drift.

Instead, we wrote and executed a string-aware Babashka static AST tracer directly on the local workspace. The script scans the source code character-by-character, keeping a mathematical record of opening and closing delimiters while completely ignoring characters inside string literals:

(let [content (slurp "full-stack-app/src/tastelocal/views/about.clj")]
  (loop [chars (seq content) depth [] line 1 col 1 in-string? false]
    (if (empty? chars)
      (do (println "Done. Depth at end:") (doseq [d depth] (println d)))
      (let [c (first chars)
            next-line (if (= c \newline) (inc line) line)
            next-col (if (= c \newline) 1 (inc col))]
        (cond
          (= c \") (recur (rest chars) depth next-line next-col (not in-string?))
          in-string? (recur (rest chars) depth next-line next-col true)
          (= c \() (recur (rest chars) (conj depth [\( line col]) next-line next-col false)
          (= c \)) (if (= (first (last depth)) \()
                     (recur (rest chars) (pop depth) next-line next-col false)
                     (do (println "Unmatched ) at line" line "col" col) (recur (rest chars) depth next-line next-col false)))
          (= c \[) (recur (rest chars) (conj depth [\[ line col]) next-line next-col false)
          (= c \]) (if (= (first (last depth)) \[)
                     (recur (rest chars) (pop depth) next-line next-col false)
                     (do (println "Unmatched ] at line" line "col" col) (recur (rest chars) depth next-line next-col false)))
          :else (recur (rest chars) depth next-line next-col false))))))

Running this script yielded a perfectly deterministic diagnosis:

Unmatched ) at line 111 col 98
Done. Depth at end:
[( 7 1]   ; (defn render)
[( 8 3]   ; (let)
[( 9 5]   ; (layout/base)
[[ 12 7]  ; [:div.container] <-- Vector open at row 12 remained unclosed

The mathematical proof was absolute. The tracer told us exactly which structural elements were orphaned. By adjusting our closing block on line 111 to precisely ]]]]))) (four brackets, three parentheses), we re-ran the static tracer and got a completely clean output:

Done. Depth at end:
[EMPTY]

Within 15 seconds of committing the mathematically aligned block, the Northflank pod built, booted, seeded its database, and returned HTTP 200 OK.


🤖 Establishing a Permanent AI Agent Determinism Rule

To collapse the entropy of all future deployments, we have locked this workflow down permanently.

We established a Global Rule inside /home/nurazhar/.gemini/config/AGENTS.md and saved a reusable trace-delimiters.clj checker. Now, whenever an AI agent works on a Clojure file in this system, the rulebook mandates that they MUST execute the static AST checks before making a Git commit.

By converting an operational struggle into a permanent, automated system constraint, we ensure that the system remains robust. Software development is not about avoiding mistakes—it is about turning mistakes into code-enforced mathematical certainties.