Release: sessioncheck 0.2

In which the author realises she doesn’t know how to write package release posts
R
Reproducibility
Package Release
Author
Published

September 13, 2026

Okay so… um. Apparently, if you are a person who writes and maintains software that other people might be using, it is considered a politeness to write up some “release notes” or whatever when you update said software? Am I hearing this correctly?

Huh. Evidently that is indeed the social norm. And somehow I have become a person who writes software that other people use? That… seems like a sociological and quality control failure. You can’t go around letting people like me write software tools and then have other people actually use them. That way lies madness.

But since that has apparently happened, I had better do my part and pretend to be a professional and announce that sessioncheck version 0.2.0 is now on CRAN. Some things are the same as before. Other things are different. Such is the nature of software releases, so perhaps I should unpack this a little…

# remotes::install_version("sessioncheck", "0.2.0")
packageVersion("sessioncheck")
[1] '0.2.0'


Um… what’s sessioncheck?

The thing about writing R packages that are not dplyr or ggplot2 is that, when the maintainer writes a blog post announcing an update to said package, the typical reaction from people who have the misfortune to encounter said blog post is to ask… “who the fuck are you bitch, what the fuck is this package, and why should I even care???”1 And fair enough. Very few packages are famous and well known enough that the author can presume the reader knows what the hell any of this is about. So perhaps a brief recap is in order.

Back at the start of this year I wrote a blog post about this idea I had for a package that would assist beginner- and intermediate-level R users write their analysis scripts defensively, in a way that would hopefully stop them starting their scripts with the well-intentioned but utterly terrible rm(list=ls()) incantation. The idea behind deploying this strange and pointless piece of code at the top of the script is that – foolishly – people believe that clearing variables from the R global environment is a method of ensuring that their script will execute in a clean environment.

To anyone who has done the deep dive into the R inferno, this idea comes across as laughably foolish. It is an incantation to ward off evil spirits that has almost no power whatsoever to protect the wielder against the the eldtritch horrors that live in the R session state.2 Managing state is hard. Machines do strange things underneath the hood when you execute code, and there is almost no way you can truly protect yourself from the weirdness.

What you can do, however, is write your code in a way that scans the state of your R session and looks for hints that something might be amiss. Unless you’re the victim of a malicious attack against your machine (which I’m not going to even try to help you with), most of the things that “corrupt” your R session leave telltale signatures behind. Objects are left in the global environment. Unexpected packages have been attached. The session has been running for waaaaay longer than you think it should be. Etc. What sessioncheck does, at its core, is look for some of those telltale signs and warn the user loudly and obnoxiously that something is wrong.

There are various nuances underneath the hood, but the gist of it is this. Instead of opening your R script with rm(list=ls()), an incantation that fails silently and does more harm than good, you can use sessioncheck::sessioncheck() as the incantation, and it will annoy the fuck out of you until you fix whatever problem was making it whine at you:

sessioncheck::sessioncheck()
Warning: Session check results:
✖ Unexpected objects in global environment: blog_dir, cache_images, narrow, theme_custom, and 2 more
✔ No unexpected packages attached
✔ No unexpected environments attached

In my blog post session (which is most certainly not “clean” by any sense of the term), it detects that I’ve done something to the global environment, adding a bunch of variables that it wasn’t expecting to see if this were a truly clean session, and it whines about it in an annoying way. Indeed, it has some tools that allow you to make it even more annoying – if you call it with action = "error" the sessioncheck() function will throw an error if it detects anything it doesn’t like, and will prevent your script from doing anything.

That, of course, is the point: the true goal of sessioncheck::sessioncheck() is to annoy the shit out of the user until they clean up the R session. It doesn’t try to clean your R session for you, but it will piss you off over and over again until you do it yourself. The intent is very simple: mechanically cleaning a dirty session is very very hard. But restarting R is very easy. The purpose of the package is to annoy the user enough that they will restart R to run the script in a clean session rather than waste their time trying to figure out why their state is messed up.

Hopefully you get the idea. It is a package designed to be annoying. It is meant to force you to audit your own behaviour when you run an R script. Frankly, the package is an asshole.


Fine but why 0.2 anyway?

Okay, so now we all have some sense of what the package is about. What has changed in the new and thrilling “0.2 release” of the movie you weren’t watching anyway? Well, look, a lot of is is low-level bug fixes that you don’t care about: if you did care about such things you’d be reading the changelog. On top of that there are a few cute new features that make the output look prettier: by design the package cannot take any hard dependencies, but under the hood it’s secretly copied a few of the tricks built into cli, so it can crudely copy the gorgeous features built into cli… as long as you don’t push too hard. It’s a poor simulacrum, not the real thing. Nevertheless, you do get slightly pretter outputs in sessioncheck 0.2, which is rather nice to have. And there are a few subtle things that have been improved: you can now customise sessioncheck() to check the R working directory if you want. That wasn’t there before. But again, that’s tinkering at the edges.

The big new change, the one that deserves an actual blog post, is that sessioncheck now provides tools for recording and reporting the R session state. This shift pushes it a lot closer to well-trodden territory that is covered by packages like sessioninfo, which might come as a bit of a surprose. The sessioninfo package is already very, very good. It is maintained by Gábor Csárdi, and I trust him implicitly. I have been a Gábor Csárdi fangirl for a long time now. There is no version of Danielle that writes better code than Gábor. However, there are nuances… the goal of sessioninfo is slightly different to the goal of sessioncheck, and the things I need my package to report (in my specific industry context) are subtly different from what sessioninfo does. So……..

Yeah. Let’s talk nuance, babes…


A new sessionstate() function

The most substantial change to sessioncheck in the 0.2 release is that it includes new reporting functionality that is similar to the “session info” capabilities that are built into the base R utils::sessionInfo() function and the tidyverse/r-lib analog, sessioninfo::session_info(). I spent a lot of time arguing with myself over whether this was worth doing. There are already two perfectly serviceable tools in R that provide a summary of the R session state. Given this, I had a lot of questions about whether it was really worth developing sessioncheck::sessionstate() as an alternative. I’ve tentatively come down on the side of “yes, it’s worth it, but some caution is needed”, but to explain why I’ve ended up here it is helpful to provide some context.

One of the peculiar features you will find in recent versions of sessioninfo is that – depending on the context in which you’re calling the session_info() function – you may or may not see that it reports the hostname as part of the summary. On my work machines, this information is displayed. On my personal laptop, it is not. You can discover the reason for this by browsing the github logs associated with PR #99 that was merged into the 1.2.3 release.3 That pull request (and the corresponding issue that it closed) was made by my Certara colleague, James Craig, and was motivated by the same line of reasoning that pushed me down this path. In the regulatory context within which most pharmaceutical companies work, we have very strict rules about how and where code is permitted to be executed. If you are preparing an analysis script for submission to the US FDA, for example, you cannot run the script on whatever machine you feel like. It has to be executed on a machine that implements a “validated computing environment”. What constitutes a “validated” environment is itself a tricky question, but above all else the key thing is that there needs to be an audit trail that clearly documents how that environment is set up. My personal laptop, as fabulous as it might be, does not meet those requirements. I simply cannot be bothered to create the level of documentation for my personal laptop that the FDA expects, and with good cause. My personal laptop is my personal laptop: I don’t use it to handle clinical trial data, and I do not expect anyone else to trust the calculations I perform on it – certainly not to the level that would be required to make decisions about whether to approve a new medication!

Given this, it is extremely useful from a regulatory perspective if the “session information” that gets reported by an analysis script includes information about the name of the machine that executed the code. It is a valuable part of the audit log, and that was what motivated James’ pull request.

But… the vast majority of people who use session_info() in their scripts are not doing regulatory work. They do not need this level of documentation, and nor do they want it. Moreover, as Gábor Csárdi correctly notes in the discussion of PR #99, there are privacy concerns associated with this disclosure. Many people would – correctly! – consider the machine name to be sensitive information that should not be bundled into the output. It is in the nature of regulatory work tha we (as analysts) sacrifice some level of privacy in order to supply a more complete audit log, and this is part and parcel of the job. Yet most people are not in that situation, and they should not be expected to accept the same level of disclosure tha we do. And so as a consequence, the rather graceful way in which Gábor handled this in the sessioninfo package was to make the hostname element opt-in (see PR #105). This was a good decision, of course, because the sessioninfo package caters to a wide variety of users with quite different needs to one another.

Noting all of the above, an obvious solution presents itself. Given that utils::sessionInfo() and sessioninfo::session_info() are both designed with a privacy-first mindset, it might not be a bad thing to have an alternative version that adopts a comprehensiveness-first mindset. For the majority of users this new version might be (a) overkill and (b) a little intrusive, but for those of us who work in regulatory environments it would be a useful tool to have at our disposal.

So it came to pass that sessioncheck acquired a new sessionstate() function. The output of this function should be very familiar to users of the existing tools (as it should be, since it borrows heavily from their design), but it contains a lot of additional information that those tools deliberately do not capture. Here’s what the output looks like:

sessioncheck::sessionstate()
─ Platform ─────────────────────────────────────────────────────────────────
• version             R version 4.6.1 (2026-06-24)
• os                  Ubuntu 24.04.4 LTS
• system              x86_64, linux-gnu
• ui                  non-interactive
• tz                  Australia/Sydney
• date                2026-09-15

─ Locale ───────────────────────────────────────────────────────────────────
• language            (unset)
• collate             en_US.UTF-8
• ctype               en_US.UTF-8

─ Matrix products ──────────────────────────────────────────────────────────
• BLAS                /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0
• LAPACK              /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0

─ Document products ────────────────────────────────────────────────────────
• pandoc              3.10
• quarto              1.10.18

─ Machine ──────────────────────────────────────────────────────────────────
• hostname            wrex
• user                danielle
• working directory   /home/danielle/GitHub/djnavarro/blog/posts/2026-09-13_sessioncheck-v0.2

─ Git ──────────────────────────────────────────────────────────────────────
• commit sha          093587f39900dbab26e90fade9f3115c4baa8838
• dirty               TRUE

─ Timing ───────────────────────────────────────────────────────────────────
• captured at         2026-09-15 09:35:02 AEST
• session uptime      1.373 sec

─ RNG state ────────────────────────────────────────────────────────────────
• kind                Mersenne-Twister
• normal kind         Inversion
• sample kind         Rejection
• seed hash           bbd1d7bd48a494a1949e11f28b605dfb

─ Library paths [n = 4] ────────────────────────────────────────────────────
• /home/danielle/R/x86_64-pc-linux-gnu-library/4.6
• /usr/local/lib/R/site-library
• /usr/lib/R/site-library
• /usr/lib/R/library

─ Packages [n = 43] (attached + loaded via namespace) ──────────────────────
      package attached loaded_version         source
         base        *          4.6.1           base
          cli                   3.6.6 RSPM (R 4.6.0)
     compiler                   4.6.1           base
     datasets        *          4.6.1           base
       digest                  0.6.39 RSPM (R 4.6.0)
        dplyr                   1.2.1 RSPM (R 4.6.0)
     evaluate                   1.0.5 RSPM (R 4.6.0)
       farver                   2.1.2 RSPM (R 4.6.0)
      fastmap                   1.2.0 RSPM (R 4.6.0)
           fs                   2.1.0 RSPM (R 4.6.0)
     generics                   0.1.4 RSPM (R 4.6.0)
      ggplot2                   4.0.3 RSPM (R 4.6.0)
         glue                   1.8.1 RSPM (R 4.6.0)
     graphics        *          4.6.1           base
    grDevices        *          4.6.1           base
         grid                   4.6.1           base
       gtable                   0.3.6 RSPM (R 4.6.0)
    htmltools                   0.5.9 RSPM (R 4.6.0)
  htmlwidgets                   1.6.4 RSPM (R 4.6.0)
     jsonlite                   2.0.0 RSPM (R 4.6.0)
        knitr                    1.51 RSPM (R 4.6.0)
    lifecycle                   1.0.5 RSPM (R 4.6.0)
     magrittr                   2.0.5 RSPM (R 4.6.0)
      methods        *          4.6.1           base
         otel                   0.2.0 RSPM (R 4.6.0)
       pillar                  1.11.1 RSPM (R 4.6.0)
    pkgconfig                   2.0.3 RSPM (R 4.6.0)
           R6                   2.6.1 RSPM (R 4.6.0)
 RColorBrewer                   1.1-3 RSPM (R 4.6.0)
        rlang                   1.3.0 CRAN (R 4.6.1)
    rmarkdown                    2.32 RSPM (R 4.6.0)
    rprojroot                   2.1.1 RSPM (R 4.6.0)
           S7                   0.2.2 RSPM (R 4.6.0)
       scales                   1.4.0 RSPM (R 4.6.0)
 sessioncheck                   0.2.0 CRAN (R 4.6.1)
        stats        *          4.6.1           base
       tibble                   3.3.1 RSPM (R 4.6.0)
   tidyselect                   1.2.1 RSPM (R 4.6.0)
        tools                   4.6.1           base
        utils        *          4.6.1           base
        vctrs                   0.7.3 RSPM (R 4.6.0)
         xfun                    0.60 RSPM (R 4.6.0)
         yaml                  2.3.12 RSPM (R 4.6.0)

─ Global environment [n = 9] ───────────────────────────────────────────────
                name     class      size
               .main  function  773.7 Kb
        theme_custom  function   72.2 Kb
 .QuartoInlineRender  function    3.6 Kb
        .Random.seed   integer    2.5 Kb
            blog_dir character 152 bytes
        cache_images   logical  56 bytes
              narrow   numeric  56 bytes
           very_wide   numeric  56 bytes
                wide   numeric  56 bytes

─ Attached environments [n = 9] ────────────────────────────────────────────
              name    type
        .GlobalEnv   other
     package:stats package
  package:graphics package
 package:grDevices package
     package:utils package
  package:datasets package
   package:methods package
         Autoloads   other
      package:base package 

In so many ways it mirrors the output of existing tools: you can see what packages are loaded, what packages are attached, you can see information about the operating system, R version, and locale. You can see where the packages were installed from, you can see the matrix algebra tools (BLAS/LAPACK) and the document preparation tools (quarto/pandoc) that the system uses.

Yet in other respects it differs from existing tools:

  • hostname and username are both displayed by default (note the privacy/sensitivity issues)
  • the working directory is displayed by default (again, possibly sensitive)
  • information about the global environment is displayed (again, possibly sensitive)
  • information about attached environments is more detailed
  • information about the git repo state is included
  • information about the RNG state is included

Under the hood, it also records a hash to fingerprint the values stored in objects in the global environment (more on this later). Each of these pieces of information is valuable in an audit context, but in some cases can pose privacy concerns. Later in the post I’ll talk about how you can redact some of this information, but the plain truth here is that the design of sessionstate() is “audit-first, privacy-second”. That differs from the “privacy-first, audit-second” design to sessioninfo::session_info().

It is critical to state this upfront (and again in the documentation, as often as possible), because users should think carefully before choosing to use sessioncheck::sessionstate(). The design makes sense in regulatory environments, and as it matures I’ll likely start using it for that purpose myself. But I would not recommend using it in a more casual context. For this blog post I don’t mind if you know that my local username is “danielle” and my machine name is “wrex”.4 But that’s a choice that I’ve made deliberately. You should not presume that this choice is right for you.


Why include this extra information?

Now that I have tried my very best to openly disclose the potential risks in using sessionstate(), I should also talk about the potential benefits. Some of these I’ve already highlighted: in a regulatory environment we do need to know the hostname of the machine that executed the code, and personally I would go slightly further and say that the username is also important: knowing who executed the code is important for audit purposes. For much the same reason, the system time also matters, in case the file metadata gets corrupted but you still need to know when the script was executed.5

But there are other things you can gain from this more detailed audit log. One thing I am particularly fond of is the fact that it reports a hash for the RNG state. A common reason for scripts becoming subtly irreproducible on later re-run is that one of the packages upon which it relies has been updated in a way that alters how the RNG is called. Tracking this down is extremely difficult to do, and… well, sometimes that’s not the reason why an output changes. The advantage to explicitly including the RNG hash in the sessionstate() output is that if two runs of the same script produce the same RNG hash, you can feel pretty confident that any differences between the two aren’t due to hidden effects on the RNG state.6

Along similar lines, the sessionstate() output will capture some basic information about the state of an enclosing git repository. Technically, this is not part of the R session itself, but again useful for an audit log if you find yourself wanting or needing additional information about the surrounding environment at the time the script was executed. It’s not something I’d expect or want the existing tools to produce, but potentially valuable in the context for which sessionstate() is designed.


Redacting the output

As I mentioned, the design to sessionstate() reverses the priorities that implicitly underpin existing tools like session_info(). The primary goal is to create an audit trail; protecting privacy is a secondary goal. But that is not to say that privacy is ignored entirely. For example, one thing that sessionstate() absolutely does not attempt to capture are system environment variables, since sometimes those can contain genuine secrets that should not be included in an audit log.7 On top of that, the print() method for sessionstate objects allows the user to suppress printing of information that they’d prefer not to disclose. The most obvious example of this is the “machine” part of the output, since that is where the hostname and username get printed. You can suppress this like so:

print(
  sessioncheck::sessionstate(),
  machine = character(0),
  packages = c("package", "loaded_version"),
  globalenv_n = 3
)
─ Platform ─────────────────────────────────────────────────────────────────
• version             R version 4.6.1 (2026-06-24)
• os                  Ubuntu 24.04.4 LTS
• system              x86_64, linux-gnu
• ui                  non-interactive
• tz                  Australia/Sydney
• date                2026-09-15

─ Locale ───────────────────────────────────────────────────────────────────
• language            (unset)
• collate             en_US.UTF-8
• ctype               en_US.UTF-8

─ Matrix products ──────────────────────────────────────────────────────────
• BLAS                /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0
• LAPACK              /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0

─ Document products ────────────────────────────────────────────────────────
• pandoc              3.10
• quarto              1.10.18

─ Machine ──────────────────────────────────────────────────────────────────

─ Git ──────────────────────────────────────────────────────────────────────
• commit sha          093587f39900dbab26e90fade9f3115c4baa8838
• dirty               TRUE

─ Timing ───────────────────────────────────────────────────────────────────
• captured at         2026-09-15 09:35:02 AEST
• session uptime      1.459 sec

─ RNG state ────────────────────────────────────────────────────────────────
• kind                Mersenne-Twister
• normal kind         Inversion
• sample kind         Rejection
• seed hash           bbd1d7bd48a494a1949e11f28b605dfb

─ Library paths [n = 4] ────────────────────────────────────────────────────
• /home/danielle/R/x86_64-pc-linux-gnu-library/4.6
• /usr/local/lib/R/site-library
• /usr/lib/R/site-library
• /usr/lib/R/library

─ Packages [n = 43] (attached + loaded via namespace) ──────────────────────
      package loaded_version
         base          4.6.1
          cli          3.6.6
     compiler          4.6.1
     datasets          4.6.1
       digest         0.6.39
        dplyr          1.2.1
     evaluate          1.0.5
       farver          2.1.2
      fastmap          1.2.0
           fs          2.1.0
     generics          0.1.4
      ggplot2          4.0.3
         glue          1.8.1
     graphics          4.6.1
    grDevices          4.6.1
         grid          4.6.1
       gtable          0.3.6
    htmltools          0.5.9
  htmlwidgets          1.6.4
     jsonlite          2.0.0
        knitr           1.51
    lifecycle          1.0.5
     magrittr          2.0.5
      methods          4.6.1
         otel          0.2.0
       pillar         1.11.1
    pkgconfig          2.0.3
           R6          2.6.1
 RColorBrewer          1.1-3
        rlang          1.3.0
    rmarkdown           2.32
    rprojroot          2.1.1
           S7          0.2.2
       scales          1.4.0
 sessioncheck          0.2.0
        stats          4.6.1
       tibble          3.3.1
   tidyselect          1.2.1
        tools          4.6.1
        utils          4.6.1
        vctrs          0.7.3
         xfun           0.60
         yaml         2.3.12

─ Global environment [n = 9] ───────────────────────────────────────────────
                name    class     size
               .main function 773.7 Kb
        theme_custom function  72.2 Kb
 .QuartoInlineRender function   3.6 Kb
 ... and 6 more (see x$globalenv)

─ Attached environments [n = 9] ────────────────────────────────────────────
              name    type
        .GlobalEnv   other
     package:stats package
  package:graphics package
 package:grDevices package
     package:utils package
  package:datasets package
   package:methods package
         Autoloads   other
      package:base package 

Here, the machine part of the output disappears entirely, leaving only a blank header. The packages part of the output is partially redacted, keeping only the package name and the version. The globalenv part of the output is limited also, showing only three variables rather than the default 10. Notice that this still leaks some potentially-private information: the libpaths are still shown, and that implicitly reveals the username.8 Under the hood, the sessionstate object still contains all the suppressed information for audit purposes: the only thing that is affected is the printed output. As a privacy-preserving mechanism is it is limited, so caution is again advised, but I think it’s still a useful feature for the package to provide.


Wrap up

For the purposes of a “oh hey there is a new version of this package” post, I think we’re good. There are other new features in the package: as mentioned earlier, sessioncheck 0.2 produces cleaner messages, fixes several mildy annoying but not very interesting bugs, and slightly extends the things you can check with the core sessioncheck() function. The new sessionstate() function is also accompanied by a compare_sessionstates() function that will show the differences between two session state snapshots, which I think is a handy feature to have.9

Footnotes

  1. Okay look. After the nightmare of writing the 48-footnote sex and gender post and maintaining a proper professional voice throughout because the fucking thing was going to be read by academics at the NHMRC who unfortunately have a great deal of power to control my everyday life, I need a fucking pressure release. As much as sessioncheck is a serious package with a serious purpose, I am now talking to my people again, not the wankers who run grant agencies, and you bitches all know what kind of uncouth and terrible woman I am. We are the R weirdos, and come hell or high water we will not be falling into the trap of talking like twats when we write about code. The whole reason sessioncheck even exists is that Jenny Bryan famously threatened to set our laptops alight if we don’t manage state properly and I intend to maintain that level of integrity and decorum as I write about my partial solution to the problem.↩︎

  2. As a former D&D player whose daughter is now obsessed with the game, I feel obligated to suggest that rm(list=ls()) is like invoking the 1st-level “protection from evil 10’ radius” spell when fighting Asmodeus. You are dead in the water if you think this will work… the Lord of Hell will walk straight through your flimsy barrier and not care about it for one second.↩︎

  3. In all honesty, I only discovered this PR after I’d independently made my own decisions about what to do with sessioncheck. It just so happens that the discussion on that PR precisely mirrored my own thinking, and it provides a very clear explanation for why I ended up making the choices I did↩︎

  4. Yes, named after the Mass Effect character. Of course named after the Mass Effect character. When this one dies I’ll probably call my next machine “thane”, after the love of my life.↩︎

  5. An estoeric extra benefit: the output of some packages, like flextable, are secretly dependent on the system time. It’s not visible unless you inspect the HTML that flextable objects generate, but they carry a hidden CSS class identifier that is generated based on system time, not the RNG state. This is frustrating from a reproducibility perspective, because while the visible output of the HTML will be invariant across runs, the CSS classes will be re-created every time you re-run the script. Recording the system time doesn’t actually allow you to reverse engineer what the CSS classes should be, since they’re created from uuid::UUIDgenerate() with use.time = TRUE, but it’s useful nevertheless to acknowledge that output artifacts are time-dependent.↩︎

  6. Note that you can’t be completely certain: all you can be sure of is that by the end of the script the RNG ends up in the exact same state. In theory it’s possible that “run 1” and “run 2” ended up in the same state after following different paths, calling the RNG in different ways, but… it’s rather unlikely to say the least.↩︎

  7. Admittedly, including credentials in system environment variables is not the safest practice, and should be avoided as much as possible. But recognising that this does happen creates a practical imperative: even given the “audit-first” mindset to sessionstate(), it is too risky to capture this information, so it doesn’t.↩︎

  8. After releasing 0.2, I noticed that I didn’t include an argument to allow users to redact libpaths. That’s an odd oversight, since almost everything else can be modified in the printed output. I’ll very likely fix that in the next update.↩︎

  9. Indeed it’s similar in spirit to the sessioninfo::session_diff() function, which I didn’t even know existed until after I’d decided that sessioncheck should come with its own session state comparison tool.↩︎

Reuse

Citation

BibTeX citation:
@online{navarro2026,
  author = {Navarro, Danielle},
  title = {Release: Sessioncheck 0.2},
  date = {2026-09-13},
  url = {https://blog.djnavarro.net/posts/2026-09-13_sessioncheck-v0.2/},
  langid = {en}
}
For attribution, please cite this work as:
Navarro, Danielle. 2026. “Release: Sessioncheck 0.2.” September 13. https://blog.djnavarro.net/posts/2026-09-13_sessioncheck-v0.2/.