siyuan-note/siyuan · error

The shorthand save notebook is unavailable. Select an…

Error message

The shorthand save notebook is unavailable. Select an opened non-encrypted notebook in Settings - Document

What it means

MoveLocalShorthands requires a valid target notebook for shorthand documents. If IsShorthandSaveBoxAvailable(boxID) reports false — the box is nil, not opened, or encrypted — the function returns i18n message 375 instructing the user to select an opened non-encrypted notebook in the document settings.

Solutions

  1. Open the target notebook or select another one in Settings - Document as the shorthand save notebook
  2. Choose a non-encrypted notebook, since encrypted notebooks are rejected as shorthand targets
  3. Update the shorthand save box configuration to a valid, opened notebook ID before moving

Example fix

// before
model.MoveLocalShorthands(closedOrEncryptedBoxID)
// after
if model.IsShorthandSaveBoxAvailable(boxID) {
    model.MoveLocalShorthands(boxID)
}
Defensive patterns

Strategy: validation

Validate before calling

const boxes = await api.getNotebooks()
const box = boxes.find(b => b.id === boxID && !b.closed)
if (!box) throw new Error('shorthand save box unavailable; select an opened non-encrypted notebook')

Type guard

function isOpenBox(b) { return b != null && !!b.id && b.closed === false }

Try / catch

try { await api.moveLocalShorthands(boxID) } catch (e) { if (String(e).includes('shorthand save notebook')) { openSettingsDocument(); } else throw e }

Prevention

When it happens

Trigger: Calling MoveLocalShorthands with a boxID that is not currently open, does not exist, or points to an encrypted notebook while the shorthand save notebook setting is unavailable.

Common situations: The configured shorthand save notebook was closed, deleted, or switched to encrypted; config points at a notebook ID from another workspace; fresh install where the shorthand save notebook was never chosen.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/90a06235e525fe9b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/shortcuts.go:58

func MoveLocalShorthands(boxID string) (retIDs []string, err error) {
	shorthandsDir := filepath.Join(util.ShortcutsPath, "shorthands")
	if !gulu.File.IsDir(shorthandsDir) {
		return
	}
	if !syncLock.TryLock() {
		err = errors.New(Conf.Language(222))
		return
	}
	defer syncLock.Unlock()

	entries, err := os.ReadDir(shorthandsDir)
	if nil != err {
		logging.LogErrorf("read dir [%s] failed: %s", shorthandsDir, err)
		return
	}
	if !IsShorthandSaveBoxAvailable(boxID) {
		err = errors.New(Conf.Language(375))
		return
	}

	assetsDir := filepath.Join(util.DataDir, "assets")
	for _, entry := range entries {
		if entry.IsDir() && "assets" == entry.Name() {
			assetsEntries, readErr := os.ReadDir(filepath.Join(shorthandsDir, entry.Name()))
			if nil != readErr {
				logging.LogErrorf("read dir [%s] failed: %s", shorthandsDir, readErr)
				continue
			}
			for _, assetEntry := range assetsEntries {
				if assetEntry.IsDir() {
					continue
				}

				p := filepath.Join(shorthandsDir, entry.Name(), assetEntry.Name())
				assetWritePath := filepath.Join(assetsDir, assetEntry.Name())

View on GitHub (pinned to 9f775e8a12)