# Emacs literate config with Guix home

**URL:** <https://forum.systemcrafters.net/t/emacs-literate-config-with-guix-home/1826>\
**Category:** Emacs\
**Tags:** guix\
**Created:** [November 6, 2025, 11:30pm UTC](https://forum.systemcrafters.net/t/emacs-literate-config-with-guix-home/1826 "2025-11-06T23:30:51Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![dgr](https://yyz1.discourse-cdn.com/flex029/user_avatar/forum.systemcrafters.net/dgr/32/394_2.png) [@dgr](https://forum.systemcrafters.net/u/dgr)\
**Post date:** [November 6, 2025, 11:30pm UTC](https://forum.systemcrafters.net/t/emacs-literate-config-with-guix-home/1826/1 "2025-11-06T23:30:51Z")

</div>

My literate config didn’t work in `guix home` and as it turned out `(org-babel-load-file (expand-file-name "~/.emacs.d/config.org"))` isn’t working on Guix as `config.org` is a symlink into the store and because the timestamp is 1970 `config.el` will not get updated.

I decided to advice `org-babel-load-file` so that tangling adds a comment with the guix store hash of the original file on top of the tangled file and every time `org-babel-load-file` gets triggered it will compare the hashes.

The end of my current `init.el`:

```lisp
;; Add personal modules directory to load path
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

;; Load the Guix aware org-babel advice
(require 'guix-org-hash)

;; This is the actual config file
(org-babel-load-file (expand-file-name "~/.emacs.d/config.org"))

```

```lisp
;;; guix-org-hash.el - Guix aware org-babel-load-file advice -*- lexical-binding: t; -*-

(require 'subr-x) ;; for when-let, string-trim, etc.

(defun guix--store-hash (path)
  "Extract the Guix store hash from PATH, if it points into /gnu/store.
Return nil otherwise."
  (when (string-match "^/gnu/store/\\([a-z0-9]+\\)-" path)
    (match-string 1 path)))

(defun guix--read-embedded-hash (el-file)
  "Read embedded Guix hash comment from EL-FILE, or nil if not present."
  (when (file-exists-p el-file)
    (with-temp-buffer
      (insert-file-contents el-file nil 0 200) ;; just the first few lines
      (when (re-search-forward "^;; Guix-hash: \\([a-z0-9]+\\)" nil t)
        (match-string 1)))))

(defun guix--maybe-retangle (org-file el-file)
  "Re-tangle ORG-FILE into EL-FILE if Guix store hash changed."
  (let* ((truename (file-truename org-file))
         (store-hash (guix--store-hash truename))
         (old-hash (guix--read-embedded-hash el-file)))
    (when (and store-hash (not (equal store-hash old-hash)))
      (message "[Guix] Re-tangling %s (hash %s → %s)"
               org-file (or old-hash "none") store-hash)
      (require 'org)
      (require 'ob-tangle)
      (org-babel-tangle-file org-file el-file)
      (with-current-buffer (find-file-noselect el-file)
        (goto-char (point-min))
        (insert (format ";; Guix-hash: %s\n" store-hash))
        (save-buffer))
      t))) ;; return t if retangled

(defun guix--babel-load-file-around (orig-fun org-file &rest args)
  "Advice around `org-babel-load-file' that is aware of Guix store hashes."
  (let* ((el-file (concat (file-name-sans-extension org-file) ".el")))
    (unless (guix--maybe-retangle org-file el-file)
      ;; if no retangle triggered, fall back to standard behavior
      (message "[Guix] Using cached %s" el-file))
    (apply orig-fun org-file args)))

(advice-add 'org-babel-load-file :around #'guix--babel-load-file-around)

(provide 'guix-org-hash)

```
