Syncing app data to guest space app fails (specified file not found error)

Question / Problem

I am syncing record data from app to another app in guest space. Process management status change is timing of sync. Code doesn't work and shows error "The specified file (id: xxx) not found.".

Current Situation

Source app has App ID 23.

Fields are :

- title (Text field)

  • summary (Text Area field)
  • file (Attachment field)

Guest space app has Space ID 3 and App ID 26. Same structure but different field codes:

- guest_title (Text)

  • guest_summary (Text Area)
  • guest_file (Attachment)

Code / Attempts

This is the JavaScript code which is used.

(function() {
  'use strict';

  kintone.events.on('app.record.detail.process.proceed', function(event) {
    if (event.nextStatus.value !== 'Synced to Guest Space') {
    return event;
  }

  let record = event.record;
  //Get first file only
  let fileKey = record.file.value[0].fileKey;

  let body = {
    app: 26,
    record: {
      guest_title: { value: record.title.value },
      guest_summary: { value: record.summary.value },
      guest_file: { value: [ { fileKey: fileKey } ] }
    }
  };

// Guest space app endpoint

  return kintone.api('/k/guest/3/v1/record', 'POST', body).then(function(resp) {
    console.log('Copied to guest space', resp);
    return event;
  }).catch(function(err) {
    console.error(err);
    return event;
  });
 });
})();

Error Message

After the process is proceeded, I get the following error:

code: "GAIA_BL01"
id: "{long string}"
message:"The specified file (id: {long string}) not found."

I don't know what "specified file" error message is pointing to.

Screenshot shows before and after process status change

I just noticed, error is shown but status proceeds to next step. That is also not good outcome.

App is also not synced to guest app.

Expected Outcome

  • No error shown in console
  • App data synced to guest app
  • Status change cancels if error appears

Hello @zebra_taxi

I was able to reproduce the issue with a similar setup.

The error appears to be caused by reusing the fileKey from the attachment field in the source record.

The fileKey retrieved from an existing Attachment field cannot be used directly to attach that file to another record. To copy the attachment, you first need to download the original file using its fileKey, upload the file again using the Upload File API, and then use the new fileKey returned by the Upload File API when creating the record in the guest space app.

Also, the File Download and Upload APIs cannot be called using kintone.api(), so you can use XMLHttpRequest or the Fetch API for those requests.

I tested the following approach with a source app and an app inside a guest space, and I was able to copy both the field value and attachment successfully:

(function () {
  'use strict';

  kintone.events.on('app.record.detail.process.proceed', function (event) {
    if (event.nextStatus.value !== 'Synced to Guest Space') {
      return event;
    }

    const record = event.record;

    if (!record.file.value.length) {
      event.error = 'No attachment was found.';
      return event;
    }

    const sourceFile = record.file.value[0];

    // Download the original attachment
    const downloadFile = function () {
      return new Promise(function (resolve, reject) {
        const params = {
          fileKey: sourceFile.fileKey
        };

        const url = kintone.api.urlForGet('/k/v1/file.json', params);

        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';

        xhr.onload = function () {
          if (xhr.status === 200) {
            resolve(xhr.response);
          } else {
            reject(new Error('File download failed.'));
          }
        };

        xhr.onerror = function () {
          reject(new Error('File download failed.'));
        };

        xhr.send();
      });
    };

    // Upload the file again to obtain a new fileKey
    const uploadFile = function (blob) {
      return new Promise(function (resolve, reject) {
        const formData = new FormData();

        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', blob, sourceFile.name);

        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/k/v1/file.json');
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

        xhr.onload = function () {
          if (xhr.status === 200) {
            resolve(JSON.parse(xhr.responseText));
          } else {
            reject(new Error('File upload failed.'));
          }
        };

        xhr.onerror = function () {
          reject(new Error('File upload failed.'));
        };

        xhr.send(formData);
      });
    };

    return downloadFile()
      .then(function (blob) {
        return uploadFile(blob);
      })
      .then(function (uploadResponse) {
        const body = {
          app: 26,
          record: {
            guest_title: {
              value: record.title.value
            },
            guest_summary: {
              value: record.summary.value
            },
            guest_file: {
              value: [
                {
                  fileKey: uploadResponse.fileKey
                }
              ]
            }
          }
        };

        return kintone.api(
          '/k/guest/3/v1/record',
          'POST',
          body
        );
      })
      .then(function (response) {
        console.log('Copied to guest space:', response);
        return event;
      })
      .catch(function (error) {
        console.error('Sync failed:', error);

        event.error =
          'The record could not be synced to the guest space app.';

        return event;
      });
  });
})();

There is also a separate reason why the status proceeded even though the sync failed in your original code.

Your catch() block logs the error and then returns the normal event object:

.catch(function(err) {
  console.error(err);
  return event;
});

This allows the Process Management action to continue. If you want to prevent the status from changing when the synchronization fails, you can set event.error in the catch() block before returning the event, as shown in the sample above.

With this approach, the record is synced first, and the status proceeds only when the synchronization completes successfully.

Please note that the sample follows your current logic of copying only the first attachment. If you need to copy multiple attachments, the file download/upload process would need to be repeated for each file.

I hope this helps.

Oh I see! Main issue was fileKey in record and fileKey returned after upload are different. I didn't read API documentation enough.

Your code works perfectly thank you very much!

Thank you also for process management control. I didn't realize process management can be stopped by showing errors. This is useful and good technique! :grinning_face: