[Bug]: Cannot open file after null reference error #2141

Closed
opened 2026-02-28 20:04:28 -06:00 by GiteaMirror · 4 comments
Owner

Originally created by @pr11me on GitHub (May 18, 2025).

Verified issue does not already exist?

  • I have searched and found no existing issue

What happened?

I was entering budget values per category in the budget view when I got the same toast visible in the screenshot. After that I was not able to enter new values in the budget column.
I rebuilt the docker container and when opening the existing file, I get:

Image

The loading spinner stays indefinitely. Since this was still the initial setup, I haven't come around making a backup yet. So I hope I can restore my work of dozens of hours somehow, please advise.

How can we reproduce the issue?

Unfortunately, I haven't checked the console when the initial error toast occured in the budget view. I surmise, it's the same error as when opening the file (see screenshot). As to how to reproduce this null reference, no idea.

Where are you hosting Actual?

Docker

What browsers are you seeing the problem on?

Chrome

Operating System

None

Originally created by @pr11me on GitHub (May 18, 2025). ### Verified issue does not already exist? - [x] I have searched and found no existing issue ### What happened? I was entering budget values per category in the budget view when I got the same toast visible in the screenshot. After that I was not able to enter new values in the budget column. I rebuilt the docker container and when opening the existing file, I get: <img width="1840" alt="Image" src="https://github.com/user-attachments/assets/59e33d0f-fdf7-46cc-a66b-67cff39fcc51" /> The loading spinner stays indefinitely. Since this was still the initial setup, I haven't come around making a backup yet. So I hope I can restore my work of dozens of hours somehow, please advise. ### How can we reproduce the issue? Unfortunately, I haven't checked the console when the initial error toast occured in the budget view. I surmise, it's the same error as when opening the file (see screenshot). As to how to reproduce this null reference, no idea. ### Where are you hosting Actual? Docker ### What browsers are you seeing the problem on? Chrome ### Operating System None
GiteaMirror added the needs infobug labels 2026-02-28 20:04:28 -06:00
Author
Owner

@pr11me commented on GitHub (May 18, 2025):

I was able to recover my data via the API as described here:
https://actualbudget.org/docs/api/#connecting-to-a-remote-server

The downloadBudget call stores the metadata.json and db.sqlite in the given dataDir. A zip of the two can be provided to the "Restore Backup" feature (https://actualbudget.org/docs/backup-restore/restore).

Though I am fine (for now), the underlying issue should be investigated, since it might happen again.

@pr11me commented on GitHub (May 18, 2025): I was able to recover my data via the API as described here: https://actualbudget.org/docs/api/#connecting-to-a-remote-server The `downloadBudget` call stores the `metadata.json` and `db.sqlite` in the given `dataDir`. A zip of the two can be provided to the "Restore Backup" feature (https://actualbudget.org/docs/backup-restore/restore). Though I am fine (for now), the underlying issue should be investigated, since it might happen again.
Author
Owner

@MatissJanis commented on GitHub (Jun 3, 2025):

👋 Great that you were able to recover the budget!

I would be super interested to assist in patching this, but I would need more information how to reproduce the issue. Unless I have that - all I can do is digging in the codebase. Here's my investigation so far:

  • the error happens here because data is null
  • only way how data can be null is if we read it as null from the database
  • but there are NO places in the source where we would write null for this table
  • which makes me think some manual modifications have been done to the local database?
@MatissJanis commented on GitHub (Jun 3, 2025): 👋 Great that you were able to recover the budget! I would be super interested to assist in patching this, but I would need more information how to reproduce the issue. Unless I have that - all I can do is digging in the codebase. Here's my investigation so far: - the error happens [here](https://github.com/actualbudget/actual/blob/790d35b5c932a437b766ef2a4d4f9b6b5f729a5b/packages/crdt/src/crdt/timestamp.ts#L67:L67) because `data` is `null` - only way how `data` can be `null` is if we read it as `null` from the database - but there are NO places in the source where we would write `null` for this table - which makes me think some manual modifications have been done to the local database?
Author
Owner

@pr11me commented on GitHub (Jun 3, 2025):

Hi, no manual db modifications I'm afraid, just using the app :)

Looking at the code, data can also be null if the argument to deserializeClock is null since JSON#parse will cast this to "null" and then return null.

Suspcious places wrt a possible null Clock object are here, and the loadClock() function. If you can guarantee that the clock string is never null here at runtime, then I guess the db got corrupted somehow 🤷🏻

Two suggestions to make the code more robust:

  • change the signature of Timestamp#parse to parse(timestamp: string | Timestamp | undefined): Timestamp | null and call it in deserializeClock with Timestamp.parse(data?.timestamp), which should return null in case data is not defined. Then, deserializeClock would at least throw Timestamp.InvalidError instead of the generic TypeError which is a win :)
  • assign fallback value from catch in case parsing returns nothing
  const fallbackClock = {
     timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(),
     merkle: {},
   };
  let parsedClock = fallbackClock;
 try {
   parsedClock = JSON.parse(clockStr) ?? fallbackClock;
 } catch (e) {
   logger.error(`Could not parse clock value ${clockStr}.`);
 }

Since I cannot reproduce the error, you can close the issue if you cannot reproduce it/find anything. Thanks.

@pr11me commented on GitHub (Jun 3, 2025): Hi, no manual db modifications I'm afraid, just using the app :) Looking at the code, `data` can also be `null` if the argument to `deserializeClock` is `null` since `JSON#parse` will cast this to `"null"` and then return `null`. Suspcious places wrt a possible `null` `Clock` object are [here](https://github.com/actualbudget/actual/blob/1c46655e30083377b3c2a93a2577c60a550e3e6d/packages/loot-core/src/server/sync/index.ts#L753), and the [`loadClock()`](https://github.com/actualbudget/actual/blob/1c46655e30083377b3c2a93a2577c60a550e3e6d/packages/loot-core/src/server/db/index.ts#L91) function. If you can guarantee that the `clock` string is never `null` here at runtime, then I guess the db got corrupted somehow 🤷🏻 Two suggestions to make the code more robust: - change the signature of `Timestamp#parse` to `parse(timestamp: string | Timestamp | undefined): Timestamp | null` and call it in `deserializeClock` with `Timestamp.parse(data?.timestamp)`, which should return `null` in case data is not defined. Then, `deserializeClock` would at least throw `Timestamp.InvalidError` instead of the generic `TypeError` which is a win :) - assign fallback value from catch in case parsing returns nothing ``` const fallbackClock = { timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(), merkle: {}, }; let parsedClock = fallbackClock; try { parsedClock = JSON.parse(clockStr) ?? fallbackClock; } catch (e) { logger.error(`Could not parse clock value ${clockStr}.`); } ``` Since I cannot reproduce the error, you can close the issue if you cannot reproduce it/find anything. Thanks.
Author
Owner

@MatissJanis commented on GitHub (Jun 5, 2025):

I was looking at all the places where we write in to this table. All of them use serializeClock which can never be null.

This makes me believe there is something else going on (manual db modification? some db utility script? something else on your machine running that caused the corruption?).

@MatissJanis commented on GitHub (Jun 5, 2025): I was looking at all the places where we **write** in to this table. All of them use `serializeClock` which can never be `null`. This makes me believe there is something else going on (manual db modification? some db utility script? something else on your machine running that caused the corruption?).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#2141