<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>operationnull.com</title><id>https://operationnull.com/atom.xml</id><subtitle>Recent Posts</subtitle><updated>2026-04-24T13:06:47Z</updated><link href="https://operationnull.com/atom.xml" rel="self" /><link href="https://operationnull.com" /><entry><title>Local LLMs as the glue for complex Emacs functions</title><id>https://operationnull.com/local-llms-as-the-glue-for-complex-emacs-functions.html</id><author><name>bdunahu</name></author><updated>2026-04-05T20:58:00Z</updated><link href="https://operationnull.com/local-llms-as-the-glue-for-complex-emacs-functions.html" rel="alternate" /><content type="html">&lt;p&gt;I do all of my &lt;a href=&quot;https://codeberg.org/bdunahu/kolwynia&quot;&gt;development&lt;/a&gt; on a laptop, so running sophisticated models locally isn't really an option for me.&lt;/p&gt;&lt;p&gt;I've run small models with &lt;code&gt;llama-cpp&lt;/code&gt;, the &lt;code&gt;Qwen4B&lt;/code&gt; model, and &lt;a href=&quot;https://github.com/karthink/gptel&quot;&gt;&lt;code&gt;gptel&lt;/code&gt;&lt;/a&gt;, an LLM client for emacs. In that time, I hadn't considered it useful for any tasks.&lt;/p&gt;&lt;p&gt;But then I realized; &lt;code&gt;gptel&lt;/code&gt; supports tools written using &lt;code&gt;elisp&lt;/code&gt;!&lt;/p&gt;&lt;p&gt;This post is about my initial experiments with the function, since I am trying to figure out if I can integrate agents a little more into my workflow. I tried my first tool from &lt;code&gt;gptel&lt;/code&gt;'s README:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(gptel-make-tool
 :name &amp;quot;read_buffer&amp;quot;
 :function (lambda (buffer)
             (unless (buffer-live-p (get-buffer buffer))
               (error &amp;quot;error: buffer %s is not live.&amp;quot; buffer))
             (with-current-buffer  buffer
               (buffer-substring-no-properties (point-min) (point-max))))
 :description &amp;quot;given a buffer name, returns its contents.&amp;quot;
 :args (list '(:name &amp;quot;buffer&amp;quot;
                     :type string
                     :description &amp;quot;the name of the buffer whose contents are to be
retrieved&amp;quot;))
 :category &amp;quot;emacs&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That function allows the model to read the contents of any open file, terminal, browser tab, etc. given its exact name. Even the Qwen4B model was able to answer questions like: &amp;quot;What is in &lt;code&gt;name-of-buffer&lt;/code&gt;&amp;quot;. Of course, I had to supply the name of the buffer it would pass into the function. That hardly is useful to me!&lt;/p&gt;&lt;h1&gt;Inferring Correct Buffer given Mode&lt;/h1&gt;&lt;p&gt;It becomes an &amp;quot;agent&amp;quot; when it's able to make a decision which would be hard to do programatically. So I introduced a new tool, which wrapped a function I made to return a list of buffer names given a &lt;code&gt;major-mode&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun bd/get-buffers-of-mode (mode)
  &amp;quot;Returns a list of buffers matching mode. This has few uses outside of a
gptel tool.&amp;quot;
  (let ((buffers '()))
    (dolist (buf (buffer-list) buffers)
      (with-current-buffer buf
        (when (eq mode major-mode)
          (push buf buffers))))))

(gptel-make-tool
   :name &amp;quot;get_buffers_of_major_mode&amp;quot;
   :function (lambda (mode)
               (mapconcat (lambda (buf) (buffer-name buf))
                          (bd/get-buffers-of-mode (intern mode))
                          &amp;quot;, &amp;quot;))
   :description &amp;quot;Given the name of an emacs mode (i.e., org-mode), returns a
comma-delimited list of buffer names.&amp;quot;
   :args (list '(:name &amp;quot;mode&amp;quot;
                       :type string
                       :description &amp;quot;the name of an emacs major mode to search
for&amp;quot;))
   :category &amp;quot;emacs&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now I could ask:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;I remember I have a buffer in &lt;code&gt;markdown-mode&lt;/code&gt; which talked about experimenting with creating tools for emacs &lt;code&gt;gptel&lt;/code&gt;. What date was it created?&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;The buffer is named &lt;code&gt;playing_with_gptel_tools.md&lt;/code&gt; and the date listed in the contents is &lt;strong&gt;2026-04-05 20:58&lt;/strong&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;With a function that reads the value of &lt;code&gt;recentf-list&lt;/code&gt;, it's very likely a similar function would work for files which have been closed, and are not in a buffer! You could have a function which returns the most recent file names from the list, then one that, when given a file, opens it in the background and reads the contents.&lt;/p&gt;&lt;h1&gt;Logging Tool Use&lt;/h1&gt;&lt;p&gt;I decided, for debugging purposes as well as safety, I would like to log all the tool calls the agent makes. I didn't see any sort of function provided by &lt;code&gt;gptel&lt;/code&gt; that would let me do this.&lt;/p&gt;&lt;p&gt;So, I introduced the following code, and rewrote my current tools:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defvar bd/llama-cpp-tools-buffer-name &amp;quot;*llama-cpp-tools*&amp;quot;)
(defmacro log-tool-use (name f)
  &amp;quot;Call F with ARGS, logging the call to BD/LLAMA-CPP-TOOLS-BUFFER-NAME.&amp;quot;
  `(lambda (&amp;amp;rest args)
     (let ((str (format &amp;quot;Used tool %s with args: %S\n&amp;quot; ,name args)))
       (with-current-buffer (get-buffer-create
                             bd/llama-cpp-tools-buffer-name)
         (goto-char (point-max))
         (insert str))
       (apply ,f args))))

(gptel-make-tool
 :name &amp;quot;read_buffer&amp;quot;
 :function (log-tool-use
            &amp;quot;read_buffer&amp;quot;
            (lambda (buffer)
              ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The previous query, run again, prints to the log buffer:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Used tool get_buffers_of_major_mode with args: (&amp;quot;markdown-mode&amp;quot;)
Used tool read_buffer with args: (&amp;quot;playing_with_gptel_tools.md&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;Querying Emacs Documentation&lt;/h1&gt;&lt;p&gt;Here, I often need to look up example usages of emacs functions after reading the docs. Being the self-documenting text editor, it makes sense if the LLM knows how to use it too!:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(gptel-make-tool
 :name &amp;quot;describe_function_or_variable&amp;quot;
 :function (log-tool-use
            &amp;quot;describe_function_or_variable&amp;quot;
            (lambda (symbol-str)
              (let* ((symbol (intern symbol-str))
                     (found (cl-some (lambda (x)
                                       (funcall (nth 1 x)
                                                symbol))
                                     describe-symbol-backends)))
                (unless found
                  (error &amp;quot;error: symbol %s does not exist.&amp;quot; symbol-str))
                (string-join
                 (delq nil
                       (list
                        (when (fboundp symbol)
                          (documentation symbol t))
                        (when (boundp symbol)
                          (documentation-property symbol
                                                  'variable-documentation
                                                  t))
                        (when (facep symbol)
                          (face-documentation symbol))))
                 &amp;quot;\n--\n&amp;quot;))))
 :description &amp;quot;Given the name of a symbol (which could be a function,
variable, or face), returns its documentation.&amp;quot;
 :args (list '(:name &amp;quot;mode&amp;quot;
                     :type string
                     :description &amp;quot;the name of the symbol to be searched&amp;quot;))
 :category &amp;quot;emacs&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Show me how to use 'seq-reduce to find the longest string in a list? Don't make a function&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Here's an example of how to use &lt;code&gt;seq-reduce&lt;/code&gt; to find the longest string in a list:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(seq-reduce (lambda (current longest)
            (if (string-length current) (if (&amp;gt; (string-length current) (string-length longest)) current longest) longest))
          '(&amp;quot;apple&amp;quot; &amp;quot;banana&amp;quot; &amp;quot;cherry&amp;quot; &amp;quot;date&amp;quot;))&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;The main issue is that &lt;code&gt;string-length&lt;/code&gt; doesn't exist, and there's no INITIAL argument. So I made another tool to allow it to evaluate elisp expressions in a container:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun bd/gptel-run-command-in-container (packages command)
  (shell-command-to-string (concat &amp;quot;guix shell --container --network &amp;quot;
                                   (string-join packages &amp;quot; &amp;quot;) &amp;quot; -- &amp;quot;
                                   command)))

(gptel-make-tool
 :name &amp;quot;execute_elisp_expression&amp;quot;
 :function (bd/gptel-log-tool-use
            &amp;quot;execute_elisp_expression&amp;quot;
            (lambda (expr)
              (bd/gptel-run-command-in-container
               (list &amp;quot;emacs&amp;quot;)
               (concat &amp;quot;emacs -Q --batch --eval \&amp;quot;(princ (progn &amp;quot;
                       expr &amp;quot;))\&amp;quot;&amp;quot;))))
 :description &amp;quot;Execute an arbitrary emacs command and return the result.
Error messages from malformed expressions are returned.&amp;quot;
 :args (list '(:name &amp;quot;expression&amp;quot;
                     :type string
                     :description &amp;quot;One or more sexps to execute in an empty
emacs session.&amp;quot;))
 :category &amp;quot;execute&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I tried a lot of prompts; while it was able to lookup documentation, it didn't really seem to be able to use it to solve interpreter errors, rather than bashing it's head against the wall!&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Use 'seq-reduce to find the longest string in a list, and then test a small example using the tool (no function). You can lookup documentation for functions you are confused about. Do not respond until you have something that you have verified.&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;The issue is that &lt;code&gt;seq-reduce&lt;/code&gt; requires a function that takes two arguments, but the lambda function is being called with three. Let's fix the lambda function to take two arguments and test it again.&lt;/p&gt;&lt;p&gt;&amp;lt;off to work with the tool...&amp;gt;&lt;/p&gt;&lt;p&gt;I apologize for the repeated errors. The issue seems to be with the use of &lt;code&gt;seq-reduce&lt;/code&gt; in this context. Let me try a different approach using &lt;code&gt;reduce&lt;/code&gt; instead.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;It didn't really weave querying the documentation and trying code together as I had hoped, rather going down a rabbit hole with non-existant functions. I stopped it after around 20 iterations. My code to run stuff in a container also has a bug with escaping being applied twice; but it's safe to say coding needs a lot of parts and a well-trained agent!&lt;/p&gt;&lt;p&gt;At least, it knows what emacs is capable of, if asked directly:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;What does the &lt;code&gt;reduce&lt;/code&gt; function do?&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;I will describe the &lt;code&gt;reduce&lt;/code&gt; function for you.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;reduce&lt;/code&gt; function is not a built-in Emacs function. It is part of the Lisp programming language and is used to apply a binary function cumulatively to the elements of a sequence, reducing the sequence to a single value. If you are using Emacs Lisp, you may need to load a package or define it yourself. Let me know if you need further clarification!&lt;/p&gt;&lt;/blockquote&gt;&lt;h1&gt;Playing Music&lt;/h1&gt;&lt;p&gt;I already have a function which uses &lt;code&gt;yt-dlp&lt;/code&gt; to rip a search query (or url) off of youtube. It takes a search query. I originally tried the tool with args:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;...
:name &amp;quot;rip_something_from_yt&amp;quot;
:function (log-tool-use
           &amp;quot;rip_something_from_yt&amp;quot;
           rip-video)
:description &amp;quot;Play a video from youtube matching a string&amp;quot;
:args (list '(:name &amp;quot;unique-query&amp;quot;
                    :type string
                    :description &amp;quot;The query to search youtube with. The first
video matching the query will play. Do not search generic queries.&amp;quot;)
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The model's reasoning found it got stuck on a error... I forgot to pass it as a function rather than a variable (&lt;code&gt;#'rip-video&lt;/code&gt;)!:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Okay, the user asked to play a rock song. I used the rip_something_from_yt function with &amp;quot;rock song&amp;quot; as the query and set foreground? to false. The response was &amp;quot;void-variable rip-video&amp;quot;. Hmm, that's not a standard response. Maybe there was an error or the function didn't work as expected.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;But afterwards, it continuously ignored the hint I gave about passing in unique queries, and just passed in the generic &amp;quot;rock song&amp;quot; when asked to play a rock song.&lt;/p&gt;&lt;p&gt;But I have a large directory of local songs on my computer, with sub-directories named after each artist. So I created a tool called &lt;code&gt;get-user-artist-preferences&lt;/code&gt; which calls a shell command to find files in the directory and return a list of artists...&lt;/p&gt;&lt;p&gt;Now a command like this works!:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Can you play a rock song I would probably like?&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;I played &amp;quot;In Flames - The Wound&amp;quot; for you. Would you like to try another song from your preferred rock/ metal list, or is there a specific band you'd like to hear?&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;That song title doesn't exist by the way, but the youtube search algorithm still prioritizes a song. Sometimes it still doesn't work:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Used tool rip_something_from_yt with args: (&amp;quot;rock song from an artist I would like&amp;quot; :json-false)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That gave me &lt;a href=&quot;https://youtube.com/watch?v=vISSxIDytI0&quot;&gt;this&lt;/a&gt;, which I've never heard before, so I don't mind. The point of the tool was a little randomness anyways!&lt;/p&gt;</content></entry><entry><title>Verifying Github Actions Artifacts is not Easy</title><id>https://operationnull.com/verifying-github-actions-artifacts-is-not-easy.html</id><author><name>bdunahu</name></author><updated>2026-04-05T13:26:00Z</updated><link href="https://operationnull.com/verifying-github-actions-artifacts-is-not-easy.html" rel="alternate" /><content type="html">&lt;p&gt;I created a few shell scripts to, when given a list of URLs to Javascript/Typescript Github actions and a commit, tries to &lt;a href=&quot;https://reproducible-builds.org/&quot;&gt;reproduce&lt;/a&gt; their build artifacts in a container.&lt;/p&gt;&lt;p&gt;The list of actions I iterated over were obtained by curling the top 500-starred Github repositories and following the links to which actions and commit they brought into their build environment.&lt;/p&gt;&lt;p&gt;There is &lt;a href=&quot;./notes-on-github-actions-and-trusting-trust.html&quot;&gt;inherent trust when including a third-party action in your CI/CD pipelines&lt;/a&gt;. A developer who is especially cautious of rogue scripts &lt;a href=&quot;https://blog.gitguardian.com/compromised-tj-actions/&quot;&gt;leaking build secrets&lt;/a&gt; or compiling backdoors into their software might audit the action's source code for malware themselves. But they probably won't check that the commited build artifacts; often multi-megabyte minified Javascript files with all their dependencies baked in; actually corresponds to the source code. The inherent trust that comes with something that is libre and publicly auditable &lt;a href=&quot;https://tag-security.cncf.io/community/catalog/compromises/2018/event_stream/&quot;&gt;has been abused in ecosystems like NPM ecosystem&lt;/a&gt; to trick people into assuming the releases that are actually published aren't malware.&lt;/p&gt;&lt;p&gt;It was my assumption that, since my study was limited to actions that were used in very important software on Github (the top 500 repos) would be on their best behavior in ensuring people can build their artifacts independently and verify they get the same output. Lots of them come from verified authors, or are owned by Github themselves! At least I thought this meant they would be better than a wider &lt;a href=&quot;https://ieeexplore.ieee.org/document/9240695/&quot;&gt;study already done on the reproducibility of NPM packages&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Out of the 517 actions I tried to build (I have up to 628 more I haven't gotten through yet), 166 of them didn't provide a &lt;code&gt;package-lock.json&lt;/code&gt; file. That means that they don't adequately describe the exact dependency tree, with pinned versions, they used in their development environment to create and publish their build artifacts. In these cases, I was forced to install the newest versions of the dependencies, which automatically makes the build process non-deterministic; it depends on whatever versions happen to be available at build time to be compiled into the final distributable.&lt;/p&gt;&lt;p&gt;Furthermore, I had to dig through the build failures of over 100 actions to determine which had failed because my build scripts couldn't infer the proper build commands from those that no longer compile at all due to missing inputs or deprecated dependencies.&lt;/p&gt;&lt;p&gt;For those that my scripts failed to build or produce comparable artifacts, it's often because the commands required to build are very ad-hoc. This is a barrier towards automating verification and transparency in the Actions environment!&lt;/p&gt;&lt;p&gt;For the others; I found many forgot to declare a dependency on one of three programs: &lt;code&gt;tsc&lt;/code&gt;, &lt;code&gt;ncc&lt;/code&gt;, or &lt;code&gt;yarn&lt;/code&gt;. The number of actions which did this are in the minority of those I looked at, but I'm not sure why this is so common, and can't guarantee reproducibility without knowing which version of the compiler was used! Additionally, some actions couldn't be built anymore because they required deprecated libraries (openssl1.1). Whichever top repositories are using these actions should update to a newer commit, or drop unmaintained actions from their workflows entirely! Some actions, when inspecting the repository at the specific commit built, &lt;a href=&quot;https://github.com/cloudflare/wrangler-action/tree/v3&quot;&gt;frighteningly did not include a source tree corresponding to the artifacts at all&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;Of the 401 actions which I deemed fair to include in my study after reviewing the build logs (i.e., my scripts tried the correct build commands), only 57% produced identical ASTs as the pre-bundled artifacts. That means 43% didn't build, or produced a different output than the executables which were published.&lt;/p&gt;&lt;p&gt;I still need to manually inspect why things produced different outputs; but have noticed a trend of different variable names, and differently ordered statements. &lt;a href=&quot;https://www.npmjs.com/package/uglify-js&quot;&gt;uglify-js&lt;/a&gt; minifies Javascript by renaming variables to be as small as possible, and can possibly produce random output. For differently ordered statements, it's possible different dependency versions were used, or a similar Javascript minifier is inherently non-deterministic!&lt;/p&gt;&lt;p&gt;While I had &lt;a href=&quot;./notes-on-github-actions-and-trusting-trust.html&quot;&gt;previously thought of an idea to help automate all of this&lt;/a&gt; across Github, I find that's unlikely to be scalable. Instead, I may take a look at Docker actions, and determine the common causes for differing ASTs between Javascript builds!&lt;/p&gt;</content></entry><entry><title>Notes on Github Actions, Reproduciblity, and Trusting Trust</title><id>https://operationnull.com/notes-on-github-actions-reproduciblity-and-trusting-trust.html</id><author><name>bdunahu</name></author><updated>2026-03-10T15:27:00Z</updated><link href="https://operationnull.com/notes-on-github-actions-reproduciblity-and-trusting-trust.html" rel="alternate" /><content type="html">&lt;p&gt;I am working towards a course project related to &amp;quot;trusting trust&amp;quot; attacks in the Github CI/CD ecosystem. The idea is that Github actions have a lot of pull in supply chains, and the publication method allows malicious developers to upload compromised actions artifacts.&lt;/p&gt;&lt;p&gt;For example, &lt;a href=&quot;https://docs.github.com/en/actions/tutorials/create-actions/create-a-javascript-action#commit-tag-and-push-your-action&quot;&gt;Github prefers that all Javascript actions are minified and compressed&lt;/a&gt; to the smallest size possible, recommending &lt;code&gt;rollup.js&lt;/code&gt; to compile all dependencies into one large &lt;code&gt;.js&lt;/code&gt; file. This file format is small and portable, but not so fun for auditors. Github's trust model assumes the build artifacts developers upload match the source, but there's nothing stopping someone from injecting obfuscated JavaScript into the release, compromising the integration or deployment of pipelines which make use of it!&lt;/p&gt;&lt;p&gt;The usual solution for linking source code to build artifacts is &lt;a href=&quot;https://reproducible-builds.org/&quot;&gt;reproducible builds&lt;/a&gt;. To be done effectively, that requires the project maintainers to be extremely explicit in what source code, environment, and/or dependencies were used to produce their artifacts (named 'provenance attestations'). Under those circumstances, anyone can have some degree of certainty an artifact is legitimate by building it themselves starting from source under the same conditions and getting an output which is identical down to each individual bit.&lt;/p&gt;&lt;p&gt;Note this says nothing on whether the original source code is legitimate. It also is not true protection from 'trusting trust' attacks, because all of the independent builders could have used a compromised toolchain (i.e., a specially crafted compiler binary which always compiles in a backdoor). For the latter, you could &lt;a href=&quot;https://www.bootstrappable.org/&quot;&gt;bootstrap&lt;/a&gt; the full environment by compiling absolutely everything from &lt;a href=&quot;https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_ReflectionsonTrustingTrust.pdf&quot;&gt;source without use of a binary you didn't compile yourself&lt;/a&gt;. Nonetheless, reproduciblity is what I wanted to focus on.&lt;/p&gt;&lt;p&gt;Problem with reproducibility number two: if a Github actions maintainer truly wanted to compromise dependent projects by injecting malware into their bundled actions, they certainly would not release information on how they built their project. If the maintainer doesn't want to cooperate, it's difficult to discern if something is intentionally non-deterministic.&lt;/p&gt;&lt;p&gt;What is there to do?&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://diffoscope.org/&quot;&gt;Diffoscope&lt;/a&gt; is the standard tool for determining what differs between two sets of build artifacts. In the JavaScript example, it uses js-beautify to de-obfuscate bundled code and diff them in a readable way. In cases of unreproduciblity, it may be possible to feed this prettified output into further static analysis tools to access whether or not a payload has been introduced. Though, most papers I have read leave this as a manual process. Perhaps it would be good to leave to an LLM and assign a confidence level?&lt;/p&gt;&lt;p&gt;There are legitimate reasons why a piece of software isn't reproducible, but they can usually be solved. I've recently come across this in packaging software for GNU Guix (which supports both reproducible and bootstrappable builds), in which diffoscope also saved the day, and may write a follow up post. As for actions, the question I ask myself now is whether or not some heuristic could be automated and scaled up to cover a larger portion of the Github actions marketplace. For example, could we create a &lt;a href=&quot;https://docs.github.com/en/rest/guides/using-the-rest-api-to-interact-with-checks?apiVersion=2022-11-28&quot;&gt;Github app&lt;/a&gt; to scan new commits from tracked repositories and upload potentially evil code snippets publicly?&lt;/p&gt;</content></entry><entry><title>Pinenote</title><id>https://operationnull.com/pinenote.html</id><author><name>bdunahu</name></author><updated>2026-01-14T01:55:00Z</updated><link href="https://operationnull.com/pinenote.html" rel="alternate" /><content type="html">&lt;p&gt;I bought an experimental eink tablet for almost $500, because I like to read a lot.&lt;/p&gt;&lt;p&gt;Maybe the smugness is over the top, given the second clause doesn't succeed in following from the first. Paper and books are cheap, and reading epubs is a simple enough task on any device. But the Pinenote solves a specific problem I have more elegantly than I think any other device could, in a way that makes me curious what else I can use it to solve.&lt;/p&gt;&lt;p&gt;To get closer to the point, I've purchased a fair share of physical books off of sites like thriftbooks or biblio. While I like Emacs, I only want to use it when I need to be at the computer. Times where this is not the case is almost never. Pleasure reading is one of my few hobbies that does not require a computer, but only if I own a physical copy of the text I want to read, which again is almost never, especially because I'm reading at any time is done on a whim; I don't have a backlog to borrow or purchase.&lt;/p&gt;&lt;p&gt;It may be wrong to consider using the Pinenote a 'break' from computers, but eink feels extremely low tech. It looks like paper, gives off as much light as paper, and requires as much power as a piece of paper to keep the display on (each would-be-pixel stores a state similar to NAND flash when a charge is applied). In the pinenote's case, it almost feels like paper. I only wish it turned like paper.&lt;/p&gt;&lt;p&gt;Most importantly, the distractions of a laptop or phone are not there; eink isn't nearly fast enough for watching videos, it doesn't display color, and the frontlight eats through enough battery I am motivated to leave it off in favor of an external light. I didn't expect this kind of boon, but have even started using the device to hand write out the plot of my fiction rather than typing. Most of my usage has been limited to koreader, xournalpp, and a browser for some light research while preparing materials. In this case, Emacs as a writing environment fails to provide the most important thing: a distraction free environment.&lt;/p&gt;&lt;p&gt;In just a few days, I'm already appreciative the screen of an eink is never blank even when off; it gives the impression the device is always ready and waiting for me to use it (I make a point to leave the device 'suspended' on my desk with whatever I was doing still perfectly visible).&lt;/p&gt;&lt;p&gt;Of course; one of the main benefits is that the Pinenote is a full GNU/Linux device, which is without many restrictions and familiar enough. I'm excited to try running GNU Guix on it (I have no experience with troubleshooting or developing for ARM yet) and seeing if it can be used as a input device or drawing tablet on my laptop. I am in no rush though, for now I'm satisfied with the preinstalled software suite and much anticipated reading.&lt;/p&gt;</content></entry><entry><title>Personal Filesharing</title><id>https://operationnull.com/personal-filesharing.html</id><author><name>bdunahu</name></author><updated>2025-02-17T00:00:00Z</updated><link href="https://operationnull.com/personal-filesharing.html" rel="alternate" /><content type="html">&lt;p&gt;I previously used pastebin/filesharing sites to send code and images over IRC. However, I no longer do; after piecing together my own.&lt;/p&gt;&lt;p&gt;The code to run the pastebin is small; it would be even smaller if I were more practiced in shell scripting:&lt;/p&gt;&lt;p&gt;One script on my laptop accepts a file name and an optional time specifier in hours. It generates a new file name with a few random characters piped out of &lt;code&gt;/dev/urandom&lt;/code&gt;, concatenates it with a dash - character, and a unix timestamp representing &lt;code&gt;CURR_DATE + USER_SPECIFIED_HOURS&lt;/code&gt;. It then uses &lt;code&gt;scp&lt;/code&gt; to copy the given file to a public-facing directory on the remote server with the generated name (the directory listing itself cannot be viewed in a browser, though, since the pasted files are meant to be relatively hidden).&lt;/p&gt;&lt;p&gt;The second script on the remote server simply scans the directory every hour for files that are expired (based on the file name) and removes them.&lt;/p&gt;&lt;p&gt;Of course, hosting your own pastebin is a relatively common idea if you have a webserver and the need, (I even found some tools which set up an entire netcat server if you need a full web service). However, I myself had not yet seen anyone use filenames to get almost all of the functionality of file-hosting sites like 0x0.st. I reused the idea from Protesilaos Stavrou's Emacs 'denote' package, which also showcases the power and simplicity of embedding metadata in file names; since it remains very accessible to filtering with standard CLI tools.&lt;/p&gt;&lt;p&gt;I like being able to upload my files and share them from a space I still control. I only remembered to finish writing about this idea since it already had appreciated benefits in unexpected places; allowing me to share my University work through a link to my own site, rather than the suggested use of services such as google drive.&lt;/p&gt;&lt;p&gt;Sometimes it's easy to forget that browsers can play raw video or display pdfs when directed to a video file.&lt;/p&gt;</content></entry></feed>